diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs
index 1b854e6905..5bae4a1336 100644
--- a/Content.Client/Actions/ActionsSystem.cs
+++ b/Content.Client/Actions/ActionsSystem.cs
@@ -1,33 +1,17 @@
-using Content.Client.Actions.Assignments;
-using Content.Client.Actions.UI;
-using Content.Client.Construction;
-using Content.Client.DragDrop;
-using Content.Client.Hands;
-using Content.Client.Items.Managers;
-using Content.Client.Outline;
-using Content.Client.Popups;
+using System.IO;
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
-using Content.Shared.Input;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
-using Robust.Client.Graphics;
using Robust.Client.Player;
-using Robust.Client.UserInterface;
-using Robust.Client.Utility;
-using Robust.Shared.Audio;
using Robust.Shared.ContentPack;
using Robust.Shared.GameStates;
-using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
-using Robust.Shared.Player;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Utility;
-using System.IO;
-using System.Linq;
using YamlDotNet.RepresentationModel;
namespace Content.Client.Actions
@@ -35,268 +19,84 @@ namespace Content.Client.Actions
[UsedImplicitly]
public sealed class ActionsSystem : SharedActionsSystem
{
-
[Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IUserInterfaceManager _uiManager = default!;
- [Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
- [Dependency] private readonly ISerializationManager _serializationManager = default!;
- [Dependency] private readonly IResourceManager _resourceManager = default!;
- [Dependency] private readonly IOverlayManager _overlayMan = default!;
- [Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly InteractionOutlineSystem _interactionOutline = default!;
- [Dependency] private readonly TargetOutlineSystem _targetOutline = default!;
+ [Dependency] private readonly IResourceManager _resources = default!;
+ [Dependency] private readonly ISerializationManager _serialization = default!;
- // TODO Redo assignments, including allowing permanent user configurable slot assignments.
- ///
- /// Current assignments for all hotbars / slots for this entity.
- ///
- public ActionAssignments Assignments = new(Hotbars, Slots);
+ public event Action? OnActionAdded;
+ public event Action? OnActionRemoved;
+ public event Action? OnLinkActions;
+ public event Action? OnUnlinkActions;
+ public event Action? ClearAssignments;
+ public event Action>? AssignSlot;
- public const byte Hotbars = 9;
- public const byte Slots = 10;
-
- public bool UIDirty;
-
- public ActionsUI? Ui;
- private EntityUid? _highlightedEntity;
+ public ActionsComponent? PlayerActions { get; private set; }
public override void Initialize()
{
base.Initialize();
-
- // set up hotkeys for hotbar
- CommandBinds.Builder
- .Bind(ContentKeyFunctions.OpenActionsMenu,
- InputCmdHandler.FromDelegate(_ => ToggleActionsMenu()))
- .Bind(ContentKeyFunctions.Hotbar1,
- HandleHotbarKeybind(0))
- .Bind(ContentKeyFunctions.Hotbar2,
- HandleHotbarKeybind(1))
- .Bind(ContentKeyFunctions.Hotbar3,
- HandleHotbarKeybind(2))
- .Bind(ContentKeyFunctions.Hotbar4,
- HandleHotbarKeybind(3))
- .Bind(ContentKeyFunctions.Hotbar5,
- HandleHotbarKeybind(4))
- .Bind(ContentKeyFunctions.Hotbar6,
- HandleHotbarKeybind(5))
- .Bind(ContentKeyFunctions.Hotbar7,
- HandleHotbarKeybind(6))
- .Bind(ContentKeyFunctions.Hotbar8,
- HandleHotbarKeybind(7))
- .Bind(ContentKeyFunctions.Hotbar9,
- HandleHotbarKeybind(8))
- .Bind(ContentKeyFunctions.Hotbar0,
- HandleHotbarKeybind(9))
- .Bind(ContentKeyFunctions.Loadout1,
- HandleChangeHotbarKeybind(0))
- .Bind(ContentKeyFunctions.Loadout2,
- HandleChangeHotbarKeybind(1))
- .Bind(ContentKeyFunctions.Loadout3,
- HandleChangeHotbarKeybind(2))
- .Bind(ContentKeyFunctions.Loadout4,
- HandleChangeHotbarKeybind(3))
- .Bind(ContentKeyFunctions.Loadout5,
- HandleChangeHotbarKeybind(4))
- .Bind(ContentKeyFunctions.Loadout6,
- HandleChangeHotbarKeybind(5))
- .Bind(ContentKeyFunctions.Loadout7,
- HandleChangeHotbarKeybind(6))
- .Bind(ContentKeyFunctions.Loadout8,
- HandleChangeHotbarKeybind(7))
- .Bind(ContentKeyFunctions.Loadout9,
- HandleChangeHotbarKeybind(8))
- // when selecting a target, we intercept clicks in the game world, treating them as our target selection. We want to
- // take priority before any other systems handle the click.
- .BindBefore(EngineKeyFunctions.Use, new PointerInputCmdHandler(TargetingOnUse, outsidePrediction: true),
- typeof(ConstructionSystem), typeof(DragDropSystem))
- .BindBefore(EngineKeyFunctions.UIRightClick, new PointerInputCmdHandler(TargetingCancel, outsidePrediction: true))
- .Register();
-
SubscribeLocalEvent(OnPlayerAttached);
SubscribeLocalEvent(OnPlayerDetached);
- SubscribeLocalEvent(HandleState);
+ SubscribeLocalEvent(HandleComponentState);
}
- public override void Dirty(ActionType action)
+ private void HandleComponentState(EntityUid uid, ActionsComponent component, ref ComponentHandleState args)
{
- // Should only ever receive component states for attached player's component.
- // --> lets not bother unnecessarily dirtying and prediction-resetting actions for other players.
- if (action.AttachedEntity != _playerManager.LocalPlayer?.ControlledEntity)
+ if (args.Current is not ActionsComponentState currentState)
return;
- base.Dirty(action);
- UIDirty = true;
- }
+ List added = new();
+ List removed = new();
- private void HandleState(EntityUid uid, ActionsComponent component, ref ComponentHandleState args)
- {
- if (args.Current is not ActionsComponentState state)
- return;
-
- var serverActions = new SortedSet(state.Actions);
-
- foreach (var act in component.Actions.ToList())
+ foreach (var actionType in component.Actions)
{
- if (act.ClientExclusive)
- continue;
-
- if (!serverActions.TryGetValue(act, out var serverAct))
+ if (!currentState.Actions.Contains(actionType))
{
- component.Actions.Remove(act);
- if (act.AutoRemove && !(Ui?.Locked ?? false))
- Assignments.Remove(act);
- continue;
+ removed.Add(actionType);
}
-
- act.CopyFrom(serverAct);
- serverActions.Remove(serverAct);
}
- // Anything that remains is a new action
- foreach (var newAct in serverActions)
+ foreach (var serverAction in currentState.Actions)
{
- // We create a new action, not just sorting a reference to the state's action.
- component.Actions.Add((ActionType) newAct.Clone());
- }
-
- UIDirty = true;
- }
-
- ///
- /// Highlights the item slot (inventory or hand) that contains this item
- ///
- ///
- public void HighlightItemSlot(EntityUid item)
- {
- StopHighlightingItemSlot();
-
- _highlightedEntity = item;
- _itemSlotManager.HighlightEntity(item);
- }
-
- ///
- /// Stops highlighting any item slots we are currently highlighting.
- /// H
- public void StopHighlightingItemSlot()
- {
- if (_highlightedEntity == null)
- return;
-
- _itemSlotManager.UnHighlightEntity(_highlightedEntity.Value);
- _highlightedEntity = null;
- }
-
- protected override void AddActionInternal(ActionsComponent comp, ActionType action)
- {
- // Sometimes the client receives actions from the server, before predicting that newly added components will add
- // their own shared actions. Just in case those systems ever decided to directly access action properties (e.g.,
- // action.Toggled), we will remove duplicates:
- if (comp.Actions.TryGetValue(action, out var existing))
- {
- comp.Actions.Remove(existing);
- Assignments.Replace(existing, action);
- }
-
- comp.Actions.Add(action);
- }
-
- public override void AddAction(EntityUid uid, ActionType action, EntityUid? provider, ActionsComponent? comp = null, bool dirty = true)
- {
- if (!Resolve(uid, ref comp, false))
- return;
-
- base.AddAction(uid, action, provider, comp, dirty);
-
-
- if (uid == _playerManager.LocalPlayer?.ControlledEntity)
- UIDirty = true;
- }
-
- public override void RemoveActions(EntityUid uid, IEnumerable actions, ActionsComponent? comp = null, bool dirty = true)
- {
- if (uid != _playerManager.LocalPlayer?.ControlledEntity)
- return;
-
- if (!Resolve(uid, ref comp, false))
- return;
-
- base.RemoveActions(uid, actions, comp, dirty);
-
- foreach (var act in actions)
- {
- if (act.AutoRemove && !(Ui?.Locked ?? false))
- Assignments.Remove(act);
- }
-
- UIDirty = true;
- }
-
- public override void FrameUpdate(float frameTime)
- {
- // avoid updating GUI when doing predictions & resetting state.
- if (UIDirty)
- {
- UIDirty = false;
- UpdateUI();
- }
- }
-
- ///
- /// Updates the displayed hotbar (and menu) based on current state of actions.
- ///
- public void UpdateUI()
- {
- if (Ui == null)
- return;
-
- foreach (var action in Ui.Component.Actions)
- {
- if (action.AutoPopulate && !Assignments.Assignments.ContainsKey(action))
- Assignments.AutoPopulate(action, Ui.SelectedHotbar, false);
- }
-
- // get rid of actions that are no longer available to the user
- foreach (var (action, index) in Assignments.Assignments.ToList())
- {
- if (index.Count == 0)
+ if (!component.Actions.TryGetValue(serverAction, out var clientAction))
{
- Assignments.Assignments.Remove(action);
- continue;
+ added.Add((ActionType) serverAction.Clone());
+ }
+ else
+ {
+ clientAction.CopyFrom(serverAction);
}
-
- if (action.AutoRemove && !Ui.Locked && !Ui.Component.Actions.Contains(action))
- Assignments.ClearSlot(index[0].Hotbar, index[0].Slot, false);
}
- Assignments.PreventAutoPopulate.RemoveWhere(action => !Ui.Component.Actions.Contains(action));
+ foreach (var actionType in added)
+ {
+ component.Actions.Add(actionType);
+ OnActionAdded?.Invoke(actionType);
+ }
- Ui.UpdateUI();
- }
-
- public void HandleHotbarKeybind(byte slot, in PointerInputCmdHandler.PointerInputCmdArgs args)
- {
- Ui?.HandleHotbarKeybind(slot, args);
- }
-
- public void HandleChangeHotbarKeybind(byte hotbar, in PointerInputCmdHandler.PointerInputCmdArgs args)
- {
- Ui?.HandleChangeHotbarKeybind(hotbar, args);
- }
-
- private void OnPlayerDetached(EntityUid uid, ActionsComponent component, PlayerDetachedEvent args)
- {
- if (Ui == null) return;
- _uiManager.StateRoot.RemoveChild(Ui);
- Ui = null;
+ foreach (var actionType in removed)
+ {
+ component.Actions.Remove(actionType);
+ OnActionRemoved?.Invoke(actionType);
+ }
}
private void OnPlayerAttached(EntityUid uid, ActionsComponent component, PlayerAttachedEvent args)
{
- Assignments = new(Hotbars, Slots);
- Ui = new ActionsUI(this, component);
- _uiManager.StateRoot.AddChild(Ui);
- UIDirty = true;
+ if (uid != _playerManager.LocalPlayer?.ControlledEntity)
+ return;
+
+ OnLinkActions?.Invoke(component);
+ PlayerActions = component;
+ }
+
+ private void OnPlayerDetached(EntityUid uid, ActionsComponent component, PlayerDetachedEvent? args = null)
+ {
+ if (uid != _playerManager.LocalPlayer?.ControlledEntity)
+ return;
+
+ OnUnlinkActions?.Invoke();
+ PlayerActions = null;
}
public override void Shutdown()
@@ -305,70 +105,25 @@ namespace Content.Client.Actions
CommandBinds.Unregister();
}
- private PointerInputCmdHandler HandleHotbarKeybind(byte slot)
+ public void TriggerAction(ActionType? action)
{
- // delegate to the ActionsUI, simulating a click on it
- return new((in PointerInputCmdHandler.PointerInputCmdArgs args) =>
- {
- var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
- if (playerEntity == null ||
- !EntityManager.TryGetComponent(playerEntity.Value, out var actionsComponent)) return false;
-
- HandleHotbarKeybind(slot, args);
- return true;
- }, false);
- }
-
- private PointerInputCmdHandler HandleChangeHotbarKeybind(byte hotbar)
- {
- // delegate to the ActionsUI, simulating a click on it
- return new((in PointerInputCmdHandler.PointerInputCmdArgs args) =>
- {
- var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
- if (!EntityManager.TryGetComponent(playerEntity, out var actionsComponent)) return false;
-
- HandleChangeHotbarKeybind(hotbar, args);
- return true;
- },
- false);
- }
-
- private void ToggleActionsMenu()
- {
- Ui?.ToggleActionsMenu();
- }
-
- ///
- /// A action slot was pressed. This either performs the action or toggles the targeting mode.
- ///
- internal void OnSlotPressed(ActionSlot slot)
- {
- if (Ui == null)
+ if (PlayerActions == null || action == null || _playerManager.LocalPlayer?.ControlledEntity is not { Valid: true } user)
return;
- if (slot.Action == null || _playerManager.LocalPlayer?.ControlledEntity is not EntityUid user)
+ if (action.Provider != null && Deleted(action.Provider))
return;
- if (slot.Action.Provider != null && Deleted(slot.Action.Provider))
- return;
-
- if (slot.Action is not InstantAction instantAction)
+ if (action is not InstantAction instantAction)
{
- // for target actions, we go into "select target" mode, we don't
- // message the server until we actually pick our target.
-
- // if we're clicking the same thing we're already targeting for, then we simply cancel
- // targeting
- Ui.ToggleTargeting(slot);
return;
}
- if (slot.Action.ClientExclusive)
+ if (action.ClientExclusive)
{
if (instantAction.Event != null)
instantAction.Event.Performer = user;
- PerformAction(Ui.Component, instantAction, instantAction.Event, GameTiming.CurTime);
+ PerformAction(PlayerActions, instantAction, instantAction.Event, GameTiming.CurTime);
}
else
{
@@ -377,226 +132,6 @@ namespace Content.Client.Actions
}
}
- private bool TargetingCancel(in PointerInputCmdHandler.PointerInputCmdArgs args)
- {
- if (!GameTiming.IsFirstTimePredicted)
- return false;
-
- // only do something for actual target-based actions
- if (Ui?.SelectingTargetFor?.Action == null)
- return false;
-
- Ui.StopTargeting();
- return true;
- }
-
- ///
- /// If the user clicked somewhere, and they are currently targeting an action, try and perform it.
- ///
- private bool TargetingOnUse(in PointerInputCmdHandler.PointerInputCmdArgs args)
- {
- if (!GameTiming.IsFirstTimePredicted)
- return false;
-
- // only do something for actual target-based actions
- if (Ui?.SelectingTargetFor?.Action is not TargetedAction action)
- return false;
-
- if (_playerManager.LocalPlayer?.ControlledEntity is not EntityUid user)
- return false;
-
- if (!TryComp(user, out ActionsComponent? comp))
- return false;
-
- // Is the action currently valid?
- if (!action.Enabled
- || action.Charges != null && action.Charges == 0
- || action.Cooldown.HasValue && action.Cooldown.Value.End > GameTiming.CurTime)
- {
- // The user is targeting with this action, but it is not valid. Maybe mark this click as
- // handled and prevent further interactions.
- return !action.InteractOnMiss;
- }
-
- switch (action)
- {
- case WorldTargetAction mapTarget:
- return TryTargetWorld(args, mapTarget, user, comp) || !action.InteractOnMiss;
-
- case EntityTargetAction entTarget:
- return TargetEntity(args, entTarget, user, comp) || !action.InteractOnMiss;
-
- default:
- Logger.Error($"Unknown targeting action: {action.GetType()}");
- return false;
- }
- }
-
- private bool TryTargetWorld(in PointerInputCmdHandler.PointerInputCmdArgs args, WorldTargetAction action, EntityUid user, ActionsComponent actionComp)
- {
- var coords = args.Coordinates.ToMap(EntityManager);
-
- if (!ValidateWorldTarget(user, coords, action))
- {
- // Invalid target.
- if (action.DeselectOnMiss)
- Ui?.StopTargeting();
-
- return false;
- }
-
- if (action.ClientExclusive)
- {
- if (action.Event != null)
- {
- action.Event.Target = coords;
- action.Event.Performer = user;
- }
-
- PerformAction(actionComp, action, action.Event, GameTiming.CurTime);
- }
- else
- EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(action, coords));
-
- if (!action.Repeat)
- Ui?.StopTargeting();
-
- return true;
- }
-
- private bool TargetEntity(in PointerInputCmdHandler.PointerInputCmdArgs args, EntityTargetAction action, EntityUid user, ActionsComponent actionComp)
- {
- if (!ValidateEntityTarget(user, args.EntityUid, action))
- {
- if (action.DeselectOnMiss)
- Ui?.StopTargeting();
-
- return false;
- }
-
- if (action.ClientExclusive)
- {
- if (action.Event != null)
- {
- action.Event.Target = args.EntityUid;
- action.Event.Performer = user;
- }
-
- PerformAction(actionComp, action, action.Event, GameTiming.CurTime);
- }
- else
- EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(action, args.EntityUid));
-
- if (!action.Repeat)
- Ui?.StopTargeting();
-
- return true;
- }
-
- ///
- /// Execute convenience functionality for actions (pop-ups, sound, speech)
- ///
- protected override bool PerformBasicActions(EntityUid user, ActionType action)
- {
- var performedAction = action.Sound != null
- || !string.IsNullOrWhiteSpace(action.UserPopup)
- || !string.IsNullOrWhiteSpace(action.Popup);
-
- if (!GameTiming.IsFirstTimePredicted)
- return performedAction;
-
- if (!string.IsNullOrWhiteSpace(action.UserPopup))
- {
- var msg = (!action.Toggled || string.IsNullOrWhiteSpace(action.PopupToggleSuffix))
- ? Loc.GetString(action.UserPopup)
- : Loc.GetString(action.UserPopup + action.PopupToggleSuffix);
-
- _popupSystem.PopupEntity(msg, user);
- }
- else if (!string.IsNullOrWhiteSpace(action.Popup))
- {
- var msg = (!action.Toggled || string.IsNullOrWhiteSpace(action.PopupToggleSuffix))
- ? Loc.GetString(action.Popup)
- : Loc.GetString(action.Popup + action.PopupToggleSuffix);
-
- _popupSystem.PopupEntity(msg, user);
- }
-
- if (action.Sound != null)
- SoundSystem.Play(action.Sound.GetSound(), Filter.Local(), user, action.AudioParams);
-
- return performedAction;
- }
-
- internal void StopTargeting()
- {
- _targetOutline.Disable();
- _interactionOutline.SetEnabled(true);
-
- if (!_overlayMan.TryGetOverlay(out var handOverlay) || handOverlay == null)
- return;
-
- handOverlay.IconOverride = null;
- handOverlay.EntityOverride = null;
- }
-
- internal void StartTargeting(TargetedAction action)
- {
- // override "held-item" overlay
- if (action.TargetingIndicator
- && _overlayMan.TryGetOverlay(out var handOverlay)
- && handOverlay != null)
- {
- if (action.ItemIconStyle == ItemActionIconStyle.BigItem && action.Provider != null)
- {
- handOverlay.EntityOverride = action.Provider;
- }
- else if (action.Toggled && action.IconOn != null)
- handOverlay.IconOverride = action.IconOn.Frame0();
- else if (action.Icon != null)
- handOverlay.IconOverride = action.Icon.Frame0();
- }
-
- // TODO: allow world-targets to check valid positions. E.g., maybe:
- // - Draw a red/green ghost entity
- // - Add a yes/no checkmark where the HandItemOverlay usually is
-
- // Highlight valid entity targets
- if (action is not EntityTargetAction entityAction)
- return;
-
- Func? predicate = null;
-
- if (!entityAction.CanTargetSelf)
- predicate = e => e != entityAction.AttachedEntity;
-
- var range = entityAction.CheckCanAccess ? action.Range : -1;
-
- _interactionOutline.SetEnabled(false);
- _targetOutline.Enable(range, entityAction.CheckCanAccess, predicate, entityAction.Whitelist, null);
- }
-
- internal void TryFillSlot(byte hotbar, byte index)
- {
- if (Ui == null)
- return;
-
- var fillEvent = new FillActionSlotEvent();
- RaiseLocalEvent(Ui.Component.Owner, fillEvent, broadcast: true);
-
- if (fillEvent.Action == null)
- return;
-
- fillEvent.Action.ClientExclusive = true;
- fillEvent.Action.Temporary = true;
- fillEvent.Action.AutoPopulate = false;
-
- Ui.Component.Actions.Add(fillEvent.Action);
- Assignments.AssignSlot(hotbar, index, fillEvent.Action);
-
- Ui.UpdateUI();
- }
-
/*public void SaveActionAssignments(string path)
{
@@ -624,13 +159,13 @@ namespace Content.Client.Actions
///
public void LoadActionAssignments(string path, bool userData)
{
- if (Ui == null)
+ if (PlayerActions == null)
return;
var file = new ResourcePath(path).ToRootedPath();
TextReader reader = userData
- ? _resourceManager.UserData.OpenText(file)
- : _resourceManager.ContentFileReadText(file);
+ ? _resources.UserData.OpenText(file)
+ : _resources.ContentFileReadText(file);
var yamlStream = new YamlStream();
yamlStream.Load(reader);
@@ -638,13 +173,9 @@ namespace Content.Client.Actions
if (yamlStream.Documents[0].RootNode.ToDataNode() is not SequenceDataNode sequence)
return;
- foreach (var (action, assigns) in Assignments.Assignments)
- {
- foreach (var index in assigns)
- {
- Assignments.ClearSlot(index.Hotbar, index.Slot, true);
- }
- }
+ ClearAssignments?.Invoke();
+
+ var assignments = new List();
foreach (var entry in sequence.Sequence)
{
@@ -654,28 +185,33 @@ namespace Content.Client.Actions
if (!map.TryGet("action", out var actionNode))
continue;
- var action = _serializationManager.Read(actionNode);
+ var action = _serialization.Read(actionNode);
- if (Ui.Component.Actions.TryGetValue(action, out var existingAction))
+ if (PlayerActions.Actions.TryGetValue(action, out var existingAction))
{
existingAction.CopyFrom(action);
action = existingAction;
}
else
- Ui.Component.Actions.Add(action);
+ {
+ PlayerActions.Actions.Add(action);
+ }
if (!map.TryGet("assignments", out var assignmentNode))
continue;
- var assignments = _serializationManager.Read>(assignmentNode);
+ var nodeAssignments = _serialization.Read>(assignmentNode);
- foreach (var index in assignments)
+ foreach (var index in nodeAssignments)
{
- Assignments.AssignSlot(index.Hotbar, index.Slot, action);
+ var assignment = new SlotAssignment(index.Hotbar, index.Slot, action);
+ assignments.Add(assignment);
}
}
- UIDirty = true;
+ AssignSlot?.Invoke(assignments);
}
+
+ public record struct SlotAssignment(byte Hotbar, byte Slot, ActionType Action);
}
}
diff --git a/Content.Client/Actions/Assignments/ActionAssignments.cs b/Content.Client/Actions/Assignments/ActionAssignments.cs
deleted file mode 100644
index 5c9c178eb1..0000000000
--- a/Content.Client/Actions/Assignments/ActionAssignments.cs
+++ /dev/null
@@ -1,167 +0,0 @@
-using Content.Shared.Actions;
-using Content.Shared.Actions.ActionTypes;
-using System.Linq;
-
-namespace Content.Client.Actions.Assignments
-{
- ///
- /// Tracks and manages the hotbar assignments for actions.
- ///
- [DataDefinition]
- public sealed class ActionAssignments
- {
- // the slots and assignments fields hold client's assignments (what action goes in what slot),
- // which are completely client side and independent of what actions they've actually been granted and
- // what item the action is actually for.
-
- ///
- /// x = hotbar number, y = slot of that hotbar (index 0 corresponds to the one labeled "1",
- /// index 9 corresponds to the one labeled "0"). Essentially the inverse of _assignments.
- ///
- private readonly ActionType?[,] _slots;
-
- ///
- /// Hotbar and slot assignment for each action type (slot index 0 corresponds to the one labeled "1",
- /// slot index 9 corresponds to the one labeled "0"). The key corresponds to an index in the _slots array.
- /// The value is a list because actions can be assigned to multiple slots. Even if an action type has not been granted,
- /// it can still be assigned to a slot. Essentially the inverse of _slots.
- /// There will be no entry if there is no assignment (no empty lists in this dict)
- ///
- [DataField("assignments")]
- public readonly Dictionary> Assignments = new();
-
- ///
- /// Actions which have been manually cleared by the user, thus should not
- /// auto-populate.
- ///
- public readonly SortedSet PreventAutoPopulate = new();
-
- private readonly byte _numHotbars;
- private readonly byte _numSlots;
-
- public ActionAssignments(byte numHotbars, byte numSlots)
- {
- _numHotbars = numHotbars;
- _numSlots = numSlots;
- _slots = new ActionType?[numHotbars, numSlots];
- }
-
- public bool Remove(ActionType action) => Replace(action, null);
-
- internal bool Replace(ActionType action, ActionType? newAction)
- {
- if (!Assignments.Remove(action, out var assigns))
- return false;
-
- if (newAction != null)
- Assignments[newAction] = assigns;
-
- foreach (var (bar, slot) in assigns)
- {
- _slots[bar, slot] = newAction;
- }
-
- return true;
- }
-
- ///
- /// Assigns the indicated hotbar slot to the specified action type.
- ///
- /// hotbar whose slot is being assigned
- /// slot of the hotbar to assign to (0 = the slot labeled 1, 9 = the slot labeled 0)
- /// action to assign to the slot
- public void AssignSlot(byte hotbar, byte slot, ActionType actionType)
- {
- ClearSlot(hotbar, slot, false);
- _slots[hotbar, slot] = actionType;
- if (Assignments.TryGetValue(actionType, out var slotList))
- {
- slotList.Add((hotbar, slot));
- }
- else
- {
- var newList = new List<(byte Hotbar, byte Slot)> { (hotbar, slot) };
- Assignments[actionType] = newList;
- }
- }
-
- ///
- /// Clear the assignment from the indicated slot.
- ///
- /// hotbar whose slot is being cleared
- /// slot of the hotbar to clear (0 = the slot labeled 1, 9 = the slot labeled 0)
- /// if true, the action assigned to this slot
- /// will be prevented from being auto-populated in the future when it is newly granted.
- /// Item actions will automatically be allowed to auto populate again
- /// when their associated item are unequipped. This ensures that items that are newly
- /// picked up will always present their actions to the user even if they had earlier been cleared.
- ///
- public void ClearSlot(byte hotbar, byte slot, bool preventAutoPopulate)
- {
- // remove this particular assignment from our data structures
- // (keeping in mind something can be assigned multiple slots)
- var currentAction = _slots[hotbar, slot];
-
- if (currentAction == null)
- return;
-
- if (preventAutoPopulate)
- PreventAutoPopulate.Add(currentAction);
-
- var assignmentList = Assignments[currentAction];
- assignmentList = assignmentList.Where(a => a.Hotbar != hotbar || a.Slot != slot).ToList();
- if (!assignmentList.Any())
- {
- Assignments.Remove(currentAction);
- }
- else
- {
- Assignments[currentAction] = assignmentList;
- }
-
- _slots[hotbar, slot] = null;
- }
-
- ///
- /// Finds the next open slot the action can go in and assigns it there,
- /// starting from the currently selected hotbar.
- /// Does not update any UI elements, only updates the assignment data structures.
- ///
- /// if true, will force the assignment to occur
- /// regardless of whether this assignment has been prevented from auto population
- /// via ClearSlot's preventAutoPopulate parameter. If false, will have no effect
- /// if this assignment has been prevented from auto population.
- public void AutoPopulate(ActionType toAssign, byte currentHotbar, bool force = true)
- {
- if (!force && PreventAutoPopulate.Contains(toAssign))
- return;
-
- for (byte hotbarOffset = 0; hotbarOffset < _numHotbars; hotbarOffset++)
- {
- for (byte slot = 0; slot < _numSlots; slot++)
- {
- var hotbar = (byte) ((currentHotbar + hotbarOffset) % _numHotbars);
- var slotAssignment = _slots[hotbar, slot];
-
- if (slotAssignment != null)
- continue;
-
- AssignSlot(hotbar, slot, toAssign);
- return;
- }
- }
- // there was no empty slot
- }
-
- ///
- /// Gets the assignment to the indicated slot if there is one.
- ///
- public ActionType? this[in byte hotbar, in byte slot] => _slots[hotbar, slot];
-
- /// true if we have the assignment assigned to some slot
- public bool HasAssignment(ActionType assignment)
- {
- return Assignments.ContainsKey(assignment);
- }
- }
-}
diff --git a/Content.Client/Actions/UI/ActionMenu.cs b/Content.Client/Actions/UI/ActionMenu.cs
deleted file mode 100644
index 88d08be32c..0000000000
--- a/Content.Client/Actions/UI/ActionMenu.cs
+++ /dev/null
@@ -1,426 +0,0 @@
-using System.Globalization;
-using System.Linq;
-using System.Text;
-using System.Text.RegularExpressions;
-using Content.Client.DragDrop;
-using Content.Client.HUD;
-using Content.Client.Stylesheets;
-using Content.Shared.Actions;
-using Content.Shared.Actions.ActionTypes;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
-using Robust.Client.Utility;
-using Robust.Shared.Input;
-using Robust.Shared.Timing;
-using static Robust.Client.UserInterface.Controls.BaseButton;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
-
-namespace Content.Client.Actions.UI
-{
- ///
- /// Action selection menu, allows filtering and searching over all possible
- /// actions and populating those actions into the hotbar.
- ///
- public sealed class ActionMenu : DefaultWindow
- {
- // Pre-defined global filters that can be used to select actions based on their properties (as opposed to their
- // own yaml-defined filters).
- // TODO LOC STRINGs
- private const string AllFilter = "all";
- private const string ItemFilter = "item";
- private const string InnateFilter = "innate";
- private const string EnabledFilter = "enabled";
- private const string InstantFilter = "instant";
- private const string TargetedFilter = "targeted";
-
- private readonly string[] _filters =
- {
- AllFilter,
- ItemFilter,
- InnateFilter,
- EnabledFilter,
- InstantFilter,
- TargetedFilter
- };
-
- private const int MinSearchLength = 3;
- private static readonly Regex NonAlphanumeric = new Regex(@"\W", RegexOptions.Compiled);
- private static readonly Regex Whitespace = new Regex(@"\s+", RegexOptions.Compiled);
-
- ///
- /// Is an action currently being dragged from this window?
- ///
- public bool IsDragging => _dragDropHelper.IsDragging;
-
- private readonly ActionsUI _actionsUI;
- private readonly LineEdit _searchBar;
- private readonly MultiselectOptionButton _filterButton;
- private readonly Label _filterLabel;
- private readonly Button _clearButton;
- private readonly GridContainer _resultsGrid;
- private readonly TextureRect _dragShadow;
- private readonly IGameHud _gameHud;
- private readonly DragDropHelper _dragDropHelper;
- private readonly IEntityManager _entMan;
-
- public ActionMenu(ActionsUI actionsUI)
- {
- _actionsUI = actionsUI;
- _gameHud = IoCManager.Resolve();
- _entMan = IoCManager.Resolve();
-
- Title = Loc.GetString("ui-actionmenu-title");
- MinSize = (320, 300);
-
- Contents.AddChild(new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- Children =
- {
- new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- Children =
- {
- (_searchBar = new LineEdit
- {
- StyleClasses = { StyleNano.StyleClassActionSearchBox },
- HorizontalExpand = true,
- PlaceHolder = Loc.GetString("ui-actionmenu-search-bar-placeholder-text")
- }),
- (_filterButton = new MultiselectOptionButton()
- {
- Label = Loc.GetString("ui-actionmenu-filter-button")
- })
- }
- },
- (_clearButton = new Button
- {
- Text = Loc.GetString("ui-actionmenu-clear-button"),
- }),
- (_filterLabel = new Label()),
- new ScrollContainer
- {
- //TODO: needed? MinSize = new Vector2(200.0f, 0.0f),
- VerticalExpand = true,
- HorizontalExpand = true,
- Children =
- {
- (_resultsGrid = new GridContainer
- {
- MaxGridWidth = 300
- })
- }
- }
- }
- });
-
- foreach (var tag in _filters)
- {
- _filterButton.AddItem( CultureInfo.CurrentCulture.TextInfo.ToTitleCase(tag), tag);
- }
-
- // default to showing all actions.
- _filterButton.SelectKey(AllFilter);
-
- UpdateFilterLabel();
-
- _dragShadow = new TextureRect
- {
- MinSize = (64, 64),
- Stretch = TextureRect.StretchMode.Scale,
- Visible = false,
- SetSize = (64, 64)
- };
- UserInterfaceManager.PopupRoot.AddChild(_dragShadow);
-
- _dragDropHelper = new DragDropHelper(OnBeginActionDrag, OnContinueActionDrag, OnEndActionDrag);
- }
-
- protected override void EnteredTree()
- {
- base.EnteredTree();
- _clearButton.OnPressed += OnClearButtonPressed;
- _searchBar.OnTextChanged += OnSearchTextChanged;
- _filterButton.OnItemSelected += OnFilterItemSelected;
- _gameHud.ActionsButtonDown = true;
- }
-
- protected override void ExitedTree()
- {
- base.ExitedTree();
- _dragDropHelper.EndDrag();
- _clearButton.OnPressed -= OnClearButtonPressed;
- _searchBar.OnTextChanged -= OnSearchTextChanged;
- _filterButton.OnItemSelected -= OnFilterItemSelected;
- _gameHud.ActionsButtonDown = false;
- foreach (var actionMenuControl in _resultsGrid.Children)
- {
- var actionMenuItem = (ActionMenuItem) actionMenuControl;
- actionMenuItem.OnButtonDown -= OnItemButtonDown;
- actionMenuItem.OnButtonUp -= OnItemButtonUp;
- actionMenuItem.OnPressed -= OnItemPressed;
- }
- }
-
- private void OnFilterItemSelected(MultiselectOptionButton.ItemPressedEventArgs args)
- {
- UpdateFilterLabel();
- SearchAndDisplay();
- }
-
- protected override void Resized()
- {
- base.Resized();
- // TODO: Can rework this once https://github.com/space-wizards/RobustToolbox/issues/1392 is done,
- // currently no good way to let the grid know what size it has to "work with", so must manually resize
- _resultsGrid.MaxGridWidth = Width;
- }
-
- private bool OnBeginActionDrag()
- {
- _dragShadow.Texture = _dragDropHelper.Dragged?.Action?.Icon?.Frame0();
- // don't make visible until frameupdate, otherwise it'll flicker
- LayoutContainer.SetPosition(_dragShadow, UserInterfaceManager.MousePositionScaled.Position - (32, 32));
- return true;
- }
-
- private bool OnContinueActionDrag(float frameTime)
- {
- // keep dragged entity centered under mouse
- LayoutContainer.SetPosition(_dragShadow, UserInterfaceManager.MousePositionScaled.Position - (32, 32));
- // we don't set this visible until frameupdate, otherwise it flickers
- _dragShadow.Visible = true;
- return true;
- }
-
- private void OnEndActionDrag()
- {
- _dragShadow.Visible = false;
- }
-
- private void OnItemButtonDown(ButtonEventArgs args)
- {
- if (args.Event.Function != EngineKeyFunctions.UIClick ||
- args.Button is not ActionMenuItem action)
- {
- return;
- }
-
- _dragDropHelper.MouseDown(action);
- }
-
- private void OnItemButtonUp(ButtonEventArgs args)
- {
- // note the buttonup only fires on the control that was originally
- // pressed to initiate the drag, NOT the one we are currently hovering
- if (args.Event.Function != EngineKeyFunctions.UIClick) return;
-
- if (UserInterfaceManager.CurrentlyHovered is ActionSlot targetSlot)
- {
- if (!_dragDropHelper.IsDragging || _dragDropHelper.Dragged?.Action == null)
- {
- _dragDropHelper.EndDrag();
- return;
- }
-
- _actionsUI.System.Assignments.AssignSlot(_actionsUI.SelectedHotbar, targetSlot.SlotIndex, _dragDropHelper.Dragged.Action);
- _actionsUI.UpdateUI();
- }
-
- _dragDropHelper.EndDrag();
- }
-
- private void OnItemFocusExited(ActionMenuItem item)
- {
- // lost focus, cancel the drag if one is in progress
- _dragDropHelper.EndDrag();
- }
-
- private void OnItemPressed(ButtonEventArgs args)
- {
- if (args.Button is not ActionMenuItem actionMenuItem) return;
-
- _actionsUI.System.Assignments.AutoPopulate(actionMenuItem.Action, _actionsUI.SelectedHotbar);
- _actionsUI.UpdateUI();
- }
-
- private void OnClearButtonPressed(ButtonEventArgs args)
- {
- _searchBar.Clear();
- _filterButton.DeselectAll();
- UpdateFilterLabel();
- SearchAndDisplay();
- }
-
- private void OnSearchTextChanged(LineEdit.LineEditEventArgs obj)
- {
- SearchAndDisplay();
- }
-
- private void SearchAndDisplay()
- {
- var search = Standardize(_searchBar.Text);
- // only display nothing if there are no filters selected and text is not long enough.
- // otherwise we will search if even one filter is applied, regardless of length of search string
- if (_filterButton.SelectedKeys.Count == 0 &&
- (string.IsNullOrWhiteSpace(search) || search.Length < MinSearchLength))
- {
- ClearList();
- return;
- }
-
- var matchingActions = _actionsUI.Component.Actions
- .Where(a => MatchesSearchCriteria(a, search, _filterButton.SelectedKeys));
-
- PopulateActions(matchingActions);
- }
-
- private void UpdateFilterLabel()
- {
- if (_filterButton.SelectedKeys.Count == 0)
- {
- _filterLabel.Visible = false;
- }
- else
- {
- _filterLabel.Visible = true;
- _filterLabel.Text = Loc.GetString("ui-actionmenu-filter-label",
- ("selectedLabels", string.Join(", ", _filterButton.SelectedLabels)));
- }
- }
-
- private bool MatchesSearchCriteria(ActionType action, string standardizedSearch,
- IReadOnlyList selectedFilterTags)
- {
- // check filter tag match first - each action must contain all filter tags currently selected.
- // if no tags selected, don't check tags
- if (selectedFilterTags.Count > 0 && selectedFilterTags.Any(filterTag => !ActionMatchesFilterTag(action, filterTag)))
- {
- return false;
- }
-
- // check search tag match against the search query
- if (action.Keywords.Any(standardizedSearch.Contains))
- {
- return true;
- }
-
- if (Standardize(action.DisplayName.ToString()).Contains(standardizedSearch))
- {
- return true;
- }
-
- // search by provider name
- if (action.Provider == null || action.Provider == _actionsUI.Component.Owner)
- return false;
-
- var name = _entMan.GetComponent(action.Provider.Value).EntityName;
- return Standardize(name).Contains(standardizedSearch);
- }
-
- private bool ActionMatchesFilterTag(ActionType action, string tag)
- {
- return tag switch
- {
- EnabledFilter => action.Enabled,
- ItemFilter => action.Provider != null && action.Provider != _actionsUI.Component.Owner,
- InnateFilter => action.Provider == null || action.Provider == _actionsUI.Component.Owner,
- InstantFilter => action is InstantAction,
- TargetedFilter => action is TargetedAction,
- _ => true
- };
- }
-
- ///
- /// Standardized form is all lowercase, no non-alphanumeric characters (converted to whitespace),
- /// trimmed, 1 space max per whitespace gap,
- /// and optional spaces between case change
- ///
- private static string Standardize(string rawText, bool splitOnCaseChange = false)
- {
- rawText ??= string.Empty;
-
- // treat non-alphanumeric characters as whitespace
- rawText = NonAlphanumeric.Replace(rawText, " ");
-
- // trim spaces and reduce internal whitespaces to 1 max
- rawText = Whitespace.Replace(rawText, " ").Trim();
- if (splitOnCaseChange)
- {
- // insert a space when case switches from lower to upper
- rawText = AddSpaces(rawText, true);
- }
-
- return rawText.ToLowerInvariant().Trim();
- }
-
- // taken from https://stackoverflow.com/a/272929 (CC BY-SA 3.0)
- private static string AddSpaces(string text, bool preserveAcronyms)
- {
- if (string.IsNullOrWhiteSpace(text))
- return string.Empty;
- var newText = new StringBuilder(text.Length * 2);
- newText.Append(text[0]);
- for (var i = 1; i < text.Length; i++)
- {
- if (char.IsUpper(text[i]))
- {
- if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) ||
- (preserveAcronyms && char.IsUpper(text[i - 1]) &&
- i < text.Length - 1 && !char.IsUpper(text[i + 1])))
- newText.Append(' ');
- }
-
- newText.Append(text[i]);
- }
- return newText.ToString();
- }
-
- private void PopulateActions(IEnumerable actions)
- {
- ClearList();
-
- foreach (var action in actions)
- {
- var actionItem = new ActionMenuItem(_actionsUI, action, OnItemFocusExited);
- _resultsGrid.Children.Add(actionItem);
- actionItem.SetActionState(action.Enabled);
- actionItem.OnButtonDown += OnItemButtonDown;
- actionItem.OnButtonUp += OnItemButtonUp;
- actionItem.OnPressed += OnItemPressed;
- }
- }
-
- private void ClearList()
- {
- // TODO: Not sure if this unsub is needed if children are all being cleared
- foreach (var actionItem in _resultsGrid.Children)
- {
- ((ActionMenuItem) actionItem).OnPressed -= OnItemPressed;
- }
- _resultsGrid.Children.Clear();
- }
-
- ///
- /// Should be invoked when action states change, ensures
- /// currently displayed actions are properly showing their revoked / granted status
- ///
- public void UpdateUI()
- {
- foreach (var actionItem in _resultsGrid.Children)
- {
- var actionMenuItem = ((ActionMenuItem) actionItem);
- actionMenuItem.SetActionState(actionMenuItem.Action.Enabled);
- }
-
- SearchAndDisplay();
- }
-
- protected override void FrameUpdate(FrameEventArgs args)
- {
- base.FrameUpdate(args);
- _dragDropHelper.Update(args.DeltaSeconds);
- }
- }
-}
diff --git a/Content.Client/Actions/UI/ActionMenuItem.cs b/Content.Client/Actions/UI/ActionMenuItem.cs
deleted file mode 100644
index a2ce3023f9..0000000000
--- a/Content.Client/Actions/UI/ActionMenuItem.cs
+++ /dev/null
@@ -1,228 +0,0 @@
-using System;
-using Content.Client.Stylesheets;
-using Content.Shared.Actions;
-using Content.Shared.Actions.ActionTypes;
-using Robust.Client.GameObjects;
-using Robust.Client.Graphics;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.Utility;
-using Robust.Shared.Utility;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
-
-namespace Content.Client.Actions.UI
-{
- // TODO merge this with action-slot when it gets XAMLd
- // this has way too much overlap, especially now that they both have the item-sprite icons.
-
- ///
- /// An individual action visible in the action menu.
- ///
- public sealed class ActionMenuItem : ContainerButton
- {
- // shorter than default tooltip delay so user can
- // quickly explore what each action is
- private const float CustomTooltipDelay = 0.2f;
-
- private readonly TextureRect _bigActionIcon;
- private readonly TextureRect _smallActionIcon;
- private readonly SpriteView _smallItemSpriteView;
- private readonly SpriteView _bigItemSpriteView;
-
- public ActionType Action;
-
- private Action _onControlFocusExited;
-
- private readonly ActionsUI _actionsUI;
-
- public ActionMenuItem(ActionsUI actionsUI, ActionType action, Action onControlFocusExited)
- {
- _actionsUI = actionsUI;
- Action = action;
- _onControlFocusExited = onControlFocusExited;
-
- SetSize = (64, 64);
- VerticalAlignment = VAlignment.Top;
-
- _bigActionIcon = new TextureRect
- {
- HorizontalExpand = true,
- VerticalExpand = true,
- Stretch = TextureRect.StretchMode.Scale,
- Visible = false
- };
- _bigItemSpriteView = new SpriteView
- {
- HorizontalExpand = true,
- VerticalExpand = true,
- Scale = (2, 2),
- Visible = false,
- OverrideDirection = Direction.South,
- };
- _smallActionIcon = new TextureRect
- {
- HorizontalAlignment = HAlignment.Right,
- VerticalAlignment = VAlignment.Bottom,
- Stretch = TextureRect.StretchMode.Scale,
- Visible = false
- };
- _smallItemSpriteView = new SpriteView
- {
- HorizontalAlignment = HAlignment.Right,
- VerticalAlignment = VAlignment.Bottom,
- Visible = false,
- OverrideDirection = Direction.South,
- };
-
- // padding to the left of the small icon
- var paddingBoxItemIcon = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- HorizontalExpand = true,
- VerticalExpand = true,
- MinSize = (64, 64)
- };
- paddingBoxItemIcon.AddChild(new Control()
- {
- MinSize = (32, 32),
- });
- paddingBoxItemIcon.AddChild(new Control
- {
- Children =
- {
- _smallActionIcon,
- _smallItemSpriteView
- }
- });
- AddChild(_bigActionIcon);
- AddChild(_bigItemSpriteView);
- AddChild(paddingBoxItemIcon);
-
- TooltipDelay = CustomTooltipDelay;
- TooltipSupplier = SupplyTooltip;
- UpdateIcons();
- }
-
-
- public void UpdateIcons()
- {
- UpdateItemIcon();
-
- if (Action == null)
- {
- SetActionIcon(null);
- return;
- }
-
- if ((_actionsUI.SelectingTargetFor?.Action == Action || Action.Toggled) && Action.IconOn != null)
- SetActionIcon(Action.IconOn.Frame0());
- else
- SetActionIcon(Action.Icon?.Frame0());
- }
-
- private void SetActionIcon(Texture? texture)
- {
- if (texture == null || Action == null)
- {
- _bigActionIcon.Texture = null;
- _bigActionIcon.Visible = false;
- _smallActionIcon.Texture = null;
- _smallActionIcon.Visible = false;
- }
- else if (Action.EntityIcon != null && Action.ItemIconStyle == ItemActionIconStyle.BigItem)
- {
- _smallActionIcon.Texture = texture;
- _smallActionIcon.Modulate = Action.IconColor;
- _smallActionIcon.Visible = true;
- _bigActionIcon.Texture = null;
- _bigActionIcon.Visible = false;
- }
- else
- {
- _bigActionIcon.Texture = texture;
- _bigActionIcon.Modulate = Action.IconColor;
- _bigActionIcon.Visible = true;
- _smallActionIcon.Texture = null;
- _smallActionIcon.Visible = false;
- }
- }
-
- private void UpdateItemIcon()
- {
- if (Action?.EntityIcon == null || !IoCManager.Resolve().TryGetComponent(Action.EntityIcon.Value, out SpriteComponent? sprite))
- {
- _bigItemSpriteView.Visible = false;
- _bigItemSpriteView.Sprite = null;
- _smallItemSpriteView.Visible = false;
- _smallItemSpriteView.Sprite = null;
- }
- else
- {
- switch (Action.ItemIconStyle)
- {
- case ItemActionIconStyle.BigItem:
- _bigItemSpriteView.Visible = true;
- _bigItemSpriteView.Sprite = sprite;
- _smallItemSpriteView.Visible = false;
- _smallItemSpriteView.Sprite = null;
- break;
- case ItemActionIconStyle.BigAction:
-
- _bigItemSpriteView.Visible = false;
- _bigItemSpriteView.Sprite = null;
- _smallItemSpriteView.Visible = true;
- _smallItemSpriteView.Sprite = sprite;
- break;
-
- case ItemActionIconStyle.NoItem:
-
- _bigItemSpriteView.Visible = false;
- _bigItemSpriteView.Sprite = null;
- _smallItemSpriteView.Visible = false;
- _smallItemSpriteView.Sprite = null;
- break;
- }
- }
- }
-
- protected override void ControlFocusExited()
- {
- base.ControlFocusExited();
- _onControlFocusExited.Invoke(this);
- }
-
- private Control SupplyTooltip(Control? sender)
- {
- var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(Action.DisplayName));
- var decr = FormattedMessage.FromMarkupPermissive(Loc.GetString(Action.Description));
-
- var tooltip = new ActionAlertTooltip(name, decr);
-
- if (Action.Enabled && (Action.Charges == null || Action.Charges != 0))
- tooltip.Cooldown = Action.Cooldown;
-
- return tooltip;
- }
-
- ///
- /// Change how this is displayed depending on if it is granted or revoked
- ///
- public void SetActionState(bool granted)
- {
- if (granted)
- {
- if (HasStyleClass(StyleNano.StyleClassActionMenuItemRevoked))
- {
- RemoveStyleClass(StyleNano.StyleClassActionMenuItemRevoked);
- }
- }
- else
- {
- if (!HasStyleClass(StyleNano.StyleClassActionMenuItemRevoked))
- {
- AddStyleClass(StyleNano.StyleClassActionMenuItemRevoked);
- }
- }
- }
- }
-}
diff --git a/Content.Client/Actions/UI/ActionSlot.cs b/Content.Client/Actions/UI/ActionSlot.cs
deleted file mode 100644
index dd4ff87b62..0000000000
--- a/Content.Client/Actions/UI/ActionSlot.cs
+++ /dev/null
@@ -1,551 +0,0 @@
-using System;
-using Content.Client.Cooldown;
-using Content.Client.Stylesheets;
-using Content.Shared.Actions;
-using Content.Shared.Actions.ActionTypes;
-using Robust.Client.GameObjects;
-using Robust.Client.Graphics;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.Utility;
-using Robust.Shared.Input;
-using Robust.Shared.Timing;
-using Robust.Shared.Utility;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
-
-namespace Content.Client.Actions.UI
-{
- ///
- /// A slot in the action hotbar. Not extending BaseButton because
- /// its needs diverged too much.
- ///
- public sealed class ActionSlot : PanelContainer
- {
- // shorter than default tooltip delay so user can more easily
- // see what actions they've been given
- private const float CustomTooltipDelay = 0.5f;
-
- private static readonly string EnabledColor = "#7b7e9e";
- private static readonly string DisabledColor = "#950000";
-
- private bool _spriteViewDirty = false;
-
- ///
- /// Current action in this slot.
- ///
- public ActionType? Action { get; private set; }
-
- ///
- /// 1-10 corresponding to the number label on the slot (10 is labeled as 0)
- ///
- private byte SlotNumber => (byte) (SlotIndex + 1);
- public byte SlotIndex { get; }
-
- private readonly IGameTiming _gameTiming;
- private readonly IEntityManager _entMan;
- private readonly RichTextLabel _number;
- private readonly TextureRect _bigActionIcon;
- private readonly TextureRect _smallActionIcon;
- private readonly SpriteView _smallItemSpriteView;
- private readonly SpriteView _bigItemSpriteView;
- private readonly CooldownGraphic _cooldownGraphic;
- private readonly ActionsUI _actionsUI;
- private readonly ActionMenu _actionMenu;
- // whether button is currently pressed down by mouse or keybind down.
- private bool _depressed;
- private bool _beingHovered;
-
- ///
- /// Creates an action slot for the specified number
- ///
- /// slot index this corresponds to, 0-9 (0 labeled as 1, 8, labeled "9", 9 labeled as "0".
- public ActionSlot(ActionsUI actionsUI, ActionMenu actionMenu, byte slotIndex, IGameTiming timing, IEntityManager entMan)
- {
- _actionsUI = actionsUI;
- _actionMenu = actionMenu;
- _gameTiming = timing;
- _entMan = entMan;
- SlotIndex = slotIndex;
- MouseFilter = MouseFilterMode.Stop;
-
- SetSize = (64, 64);
- VerticalAlignment = VAlignment.Top;
- TooltipDelay = CustomTooltipDelay;
- TooltipSupplier = SupplyTooltip;
-
- _number = new RichTextLabel
- {
- StyleClasses = {StyleNano.StyleClassHotbarSlotNumber}
- };
- _number.SetMessage(SlotNumberLabel());
-
- _bigActionIcon = new TextureRect
- {
- HorizontalExpand = true,
- VerticalExpand = true,
- Stretch = TextureRect.StretchMode.Scale,
- Visible = false
- };
- _bigItemSpriteView = new SpriteView
- {
- HorizontalExpand = true,
- VerticalExpand = true,
- Scale = (2,2),
- Visible = false,
- OverrideDirection = Direction.South,
- };
- _smallActionIcon = new TextureRect
- {
- HorizontalAlignment = HAlignment.Right,
- VerticalAlignment = VAlignment.Bottom,
- Stretch = TextureRect.StretchMode.Scale,
- Visible = false
- };
- _smallItemSpriteView = new SpriteView
- {
- HorizontalAlignment = HAlignment.Right,
- VerticalAlignment = VAlignment.Bottom,
- Visible = false,
- OverrideDirection = Direction.South,
- };
-
- _cooldownGraphic = new CooldownGraphic {Progress = 0, Visible = false};
-
- // padding to the left of the number to shift it right
- var paddingBox = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- HorizontalExpand = true,
- VerticalExpand = true,
- MinSize = (64, 64)
- };
- paddingBox.AddChild(new Control()
- {
- MinSize = (4, 4),
- });
- paddingBox.AddChild(_number);
-
- // padding to the left of the small icon
- var paddingBoxItemIcon = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- HorizontalExpand = true,
- VerticalExpand = true,
- MinSize = (64, 64)
- };
- paddingBoxItemIcon.AddChild(new Control()
- {
- MinSize = (32, 32),
- });
- paddingBoxItemIcon.AddChild(new Control
- {
- Children =
- {
- _smallActionIcon,
- _smallItemSpriteView
- }
- });
- AddChild(_bigActionIcon);
- AddChild(_bigItemSpriteView);
- AddChild(_cooldownGraphic);
- AddChild(paddingBox);
- AddChild(paddingBoxItemIcon);
- DrawModeChanged();
- }
-
- private Control? SupplyTooltip(Control sender)
- {
- if (Action == null)
- return null;
-
- string? extra = null;
- if (Action.Charges != null)
- {
- extra = Loc.GetString("ui-actionslot-charges", ("charges", Action.Charges));
- }
-
- var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(Action.DisplayName));
- var decr = FormattedMessage.FromMarkupPermissive(Loc.GetString(Action.Description));
-
- var tooltip = new ActionAlertTooltip(name, decr, extra);
-
- if (Action.Enabled && (Action.Charges == null || Action.Charges != 0))
- tooltip.Cooldown = Action.Cooldown;
-
- return tooltip;
- }
-
- protected override void MouseEntered()
- {
- base.MouseEntered();
-
- _beingHovered = true;
- DrawModeChanged();
-
- if (Action?.Provider != null)
- _actionsUI.System.HighlightItemSlot(Action.Provider.Value);
- }
-
- protected override void MouseExited()
- {
- base.MouseExited();
- _beingHovered = false;
- CancelPress();
- DrawModeChanged();
- _actionsUI.System.StopHighlightingItemSlot();
- }
-
- protected override void KeyBindDown(GUIBoundKeyEventArgs args)
- {
- base.KeyBindDown(args);
-
- if (Action == null)
- {
- // No action for this slot. Maybe the user is trying to add a mapping action?
- _actionsUI.System.TryFillSlot(_actionsUI.SelectedHotbar, SlotIndex);
- return;
- }
-
- // only handle clicks, and can't do anything to this if no assignment
- if (args.Function == EngineKeyFunctions.UIClick)
- {
- // might turn into a drag or a full press if released
- Depress(true);
- _actionsUI.DragDropHelper.MouseDown(this);
- DrawModeChanged();
- return;
- }
-
- if (args.Function != EngineKeyFunctions.UIRightClick || _actionsUI.Locked)
- return;
-
- if (_actionsUI.DragDropHelper.IsDragging || _actionMenu.IsDragging)
- return;
-
- // user right clicked on an action slot, so we clear it.
- _actionsUI.System.Assignments.ClearSlot(_actionsUI.SelectedHotbar, SlotIndex, true);
-
- // If this was a temporary action, and it is no longer assigned to any slots, then we remove the action
- // altogether.
- if (Action.Temporary)
- {
- // Theres probably a better way to do this.....
- DebugTools.Assert(Action.ClientExclusive, "Temporary-actions must be client exclusive");
-
- if (!_actionsUI.System.Assignments.Assignments.TryGetValue(Action, out var index)
- || index.Count == 0)
- {
- _actionsUI.Component.Actions.Remove(Action);
- }
- }
-
- _actionsUI.StopTargeting();
- _actionsUI.UpdateUI();
- }
-
- protected override void KeyBindUp(GUIBoundKeyEventArgs args)
- {
- base.KeyBindUp(args);
-
- if (args.Function != EngineKeyFunctions.UIClick)
- return;
-
- // might be finishing a drag or using the action
- if (_actionsUI.DragDropHelper.IsDragging &&
- _actionsUI.DragDropHelper.Dragged == this &&
- UserInterfaceManager.CurrentlyHovered is ActionSlot targetSlot &&
- targetSlot != this)
- {
- // finish the drag, swap the 2 slots
- var fromIdx = SlotIndex;
- var fromAssignment = _actionsUI.System.Assignments[_actionsUI.SelectedHotbar, fromIdx];
- var toIdx = targetSlot.SlotIndex;
- var toAssignment = _actionsUI.System.Assignments[_actionsUI.SelectedHotbar, toIdx];
-
- if (fromIdx == toIdx) return;
- if (fromAssignment == null) return;
-
- _actionsUI.System.Assignments.AssignSlot(_actionsUI.SelectedHotbar, toIdx, fromAssignment);
- if (toAssignment != null)
- {
- _actionsUI.System.Assignments.AssignSlot(_actionsUI.SelectedHotbar, fromIdx, toAssignment);
- }
- else
- {
- _actionsUI.System.Assignments.ClearSlot(_actionsUI.SelectedHotbar, fromIdx, false);
- }
- _actionsUI.UpdateUI();
- }
- else
- {
- // perform the action
- if (UserInterfaceManager.CurrentlyHovered == this)
- {
- Depress(false);
- }
- }
- _actionsUI.DragDropHelper.EndDrag();
- DrawModeChanged();
- }
-
- protected override void ControlFocusExited()
- {
- // lost focus for some reason, cancel the drag if there is one.
- base.ControlFocusExited();
- _actionsUI.DragDropHelper.EndDrag();
- DrawModeChanged();
- }
-
- ///
- /// Cancel current press without triggering the action
- ///
- public void CancelPress()
- {
- _depressed = false;
- DrawModeChanged();
- }
-
- ///
- /// Press this button down. If it was depressed and now set to not depressed, will
- /// trigger the action.
- ///
- public void Depress(bool depress)
- {
- // action can still be toggled if it's allowed to stay selected
- if (Action == null || !Action.Enabled) return;
-
- if (_depressed && !depress)
- {
- // fire the action
- _actionsUI.System.OnSlotPressed(this);
- }
- _depressed = depress;
- }
-
- ///
- /// Updates the item action assigned to this slot, tied to a specific item.
- ///
- /// action to assign
- /// item the action is provided by
- public void Assign(ActionType action)
- {
- // already assigned
- if (Action != null && Action == action) return;
-
- Action = action;
- HideTooltip();
- UpdateIcons();
- DrawModeChanged();
- _number.SetMessage(SlotNumberLabel());
- }
-
- ///
- /// Clears the action assigned to this slot
- ///
- public void Clear()
- {
- if (Action == null) return;
- Action = null;
- _depressed = false;
- HideTooltip();
- UpdateIcons();
- DrawModeChanged();
- _number.SetMessage(SlotNumberLabel());
- }
-
- ///
- /// Display the action in this slot (if there is one) as enabled
- ///
- public void Enable()
- {
- DrawModeChanged();
- _number.SetMessage(SlotNumberLabel());
- }
-
- ///
- /// Display the action in this slot (if there is one) as disabled.
- /// The slot is still clickable.
- ///
- public void Disable()
- {
- _depressed = false;
- DrawModeChanged();
- _number.SetMessage(SlotNumberLabel());
- }
-
- private FormattedMessage SlotNumberLabel()
- {
- if (SlotNumber > 10) return FormattedMessage.FromMarkup("");
- var number = Loc.GetString(SlotNumber == 10 ? "0" : SlotNumber.ToString());
- var color = (Action == null || Action.Enabled) ? EnabledColor : DisabledColor;
- return FormattedMessage.FromMarkup("[color=" + color + "]" + number + "[/color]");
- }
-
- public void UpdateIcons()
- {
- UpdateItemIcon();
-
- if (Action == null)
- {
- SetActionIcon(null);
- return;
- }
-
- if ((_actionsUI.SelectingTargetFor?.Action == Action || Action.Toggled) && Action.IconOn != null)
- SetActionIcon(Action.IconOn.Frame0());
- else
- SetActionIcon(Action.Icon?.Frame0());
- }
-
- private void SetActionIcon(Texture? texture)
- {
- if (texture == null || Action == null)
- {
- _bigActionIcon.Texture = null;
- _bigActionIcon.Visible = false;
- _smallActionIcon.Texture = null;
- _smallActionIcon.Visible = false;
- }
- else if (Action.EntityIcon != null && Action.ItemIconStyle == ItemActionIconStyle.BigItem)
- {
- _smallActionIcon.Texture = texture;
- _smallActionIcon.Modulate = Action.IconColor;
- _smallActionIcon.Visible = true;
- _bigActionIcon.Texture = null;
- _bigActionIcon.Visible = false;
- }
- else
- {
- _bigActionIcon.Texture = texture;
- _bigActionIcon.Modulate = Action.IconColor;
- _bigActionIcon.Visible = true;
- _smallActionIcon.Texture = null;
- _smallActionIcon.Visible = false;
- }
- }
-
- private void UpdateItemIcon()
- {
- if (Action?.EntityIcon != null && !_entMan.EntityExists(Action.EntityIcon))
- {
- // This is almost certainly because a player received/processed their own actions component state before
- // being send the entity in their inventory that enabled this action.
-
- // Defer updating icons to the next FrameUpdate().
- _spriteViewDirty = true;
- return;
- }
-
- if (Action?.EntityIcon == null || !_entMan.TryGetComponent(Action.EntityIcon.Value, out SpriteComponent? sprite))
- {
- _bigItemSpriteView.Visible = false;
- _bigItemSpriteView.Sprite = null;
- _smallItemSpriteView.Visible = false;
- _smallItemSpriteView.Sprite = null;
- }
- else
- {
- switch (Action.ItemIconStyle)
- {
- case ItemActionIconStyle.BigItem:
- _bigItemSpriteView.Visible = true;
- _bigItemSpriteView.Sprite = sprite;
- _smallItemSpriteView.Visible = false;
- _smallItemSpriteView.Sprite = null;
- break;
- case ItemActionIconStyle.BigAction:
-
- _bigItemSpriteView.Visible = false;
- _bigItemSpriteView.Sprite = null;
- _smallItemSpriteView.Visible = true;
- _smallItemSpriteView.Sprite = sprite;
- break;
-
- case ItemActionIconStyle.NoItem:
-
- _bigItemSpriteView.Visible = false;
- _bigItemSpriteView.Sprite = null;
- _smallItemSpriteView.Visible = false;
- _smallItemSpriteView.Sprite = null;
- break;
- }
- }
- }
-
- public void DrawModeChanged()
- {
- // always show the normal empty button style if no action in this slot
- if (Action == null)
- {
- SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal);
- return;
- }
-
- // show a hover only if the action is usable or another action is being dragged on top of this
- if (_beingHovered && (_actionsUI.DragDropHelper.IsDragging || _actionMenu.IsDragging || Action.Enabled))
- {
- SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassHover);
- }
-
- // it's only depress-able if it's usable, so if we're depressed
- // show the depressed style
- if (_depressed)
- {
- SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassPressed);
- return;
- }
-
- // if it's toggled on, always show the toggled on style (currently same as depressed style)
- if (Action.Toggled || _actionsUI.SelectingTargetFor == this)
- {
- // when there's a toggle sprite, we're showing that sprite instead of highlighting this slot
- SetOnlyStylePseudoClass(Action.IconOn != null ? ContainerButton.StylePseudoClassNormal :
- ContainerButton.StylePseudoClassPressed);
- return;
- }
-
- if (!Action.Enabled)
- {
- SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassDisabled);
- return;
- }
-
- SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal);
- }
-
- protected override void FrameUpdate(FrameEventArgs args)
- {
- base.FrameUpdate(args);
-
- if (_spriteViewDirty)
- {
- _spriteViewDirty = false;
- UpdateIcons();
- }
-
- if (Action == null || Action.Cooldown == null || !Action.Enabled)
- {
- _cooldownGraphic.Visible = false;
- _cooldownGraphic.Progress = 0;
- return;
- }
-
- var cooldown = Action.Cooldown.Value;
- var duration = cooldown.End - cooldown.Start;
- var curTime = _gameTiming.CurTime;
- var length = duration.TotalSeconds;
- var progress = (curTime - cooldown.Start).TotalSeconds / length;
- var ratio = (progress <= 1 ? (1 - progress) : (curTime - cooldown.End).TotalSeconds * -5);
-
- _cooldownGraphic.Progress = MathHelper.Clamp((float)ratio, -1, 1);
- if (ratio > -1f)
- _cooldownGraphic.Visible = true;
- else
- {
- _cooldownGraphic.Visible = false;
- Action.Cooldown = null;
- DrawModeChanged();
- }
- }
- }
-}
diff --git a/Content.Client/Actions/UI/ActionsUI.cs b/Content.Client/Actions/UI/ActionsUI.cs
deleted file mode 100644
index 9cd0369221..0000000000
--- a/Content.Client/Actions/UI/ActionsUI.cs
+++ /dev/null
@@ -1,505 +0,0 @@
-using Content.Client.DragDrop;
-using Content.Client.HUD;
-using Content.Client.Resources;
-using Content.Client.Stylesheets;
-using Content.Shared.Actions;
-using Content.Shared.Actions.ActionTypes;
-using Robust.Client.Graphics;
-using Robust.Client.ResourceManagement;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.Utility;
-using Robust.Shared.Input;
-using Robust.Shared.Input.Binding;
-using Robust.Shared.Timing;
-using Robust.Shared.Utility;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
-
-namespace Content.Client.Actions.UI
-{
- ///
- /// The action hotbar on the left side of the screen.
- ///
- public sealed class ActionsUI : Container
- {
- private const float DragDeadZone = 10f;
- private const float CustomTooltipDelay = 0.4f;
- internal readonly ActionsSystem System;
- private readonly IGameHud _gameHud;
- private readonly IEntityManager _entMan;
- private readonly IGameTiming _timing;
-
- ///
- /// The action component of the currently attached entity.
- ///
- public readonly ActionsComponent Component;
-
- private readonly ActionSlot[] _slots;
-
- private readonly GridContainer _slotContainer;
-
- private readonly TextureButton _lockButton;
- private readonly TextureButton _settingsButton;
- private readonly Label _loadoutNumber;
- private readonly Texture _lockTexture;
- private readonly Texture _unlockTexture;
- private readonly BoxContainer _loadoutContainer;
-
- private readonly TextureRect _dragShadow;
-
- private readonly ActionMenu _menu;
-
- ///
- /// Index of currently selected hotbar
- ///
- public byte SelectedHotbar { get; private set; }
-
- ///
- /// Action slot we are currently selecting a target for.
- ///
- public ActionSlot? SelectingTargetFor { get; private set; }
-
- ///
- /// Drag drop helper for coordinating drag drops between action slots
- ///
- public DragDropHelper DragDropHelper { get; }
-
- ///
- /// Whether the bar is currently locked by the user. This is intended to prevent drag / drop
- /// and right click clearing slots. Anything else is still doable.
- ///
- public bool Locked { get; private set; }
-
- ///
- /// All the action slots in order.
- ///
- public IEnumerable Slots => _slots;
-
- public ActionsUI(ActionsSystem system, ActionsComponent component)
- {
- SetValue(LayoutContainer.DebugProperty, true);
- System = system;
- Component = component;
- _gameHud = IoCManager.Resolve();
- _timing = IoCManager.Resolve();
- _entMan = IoCManager.Resolve();
- _menu = new ActionMenu(this);
-
- LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.End);
- LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.Constrain);
- LayoutContainer.SetAnchorTop(this, 0f);
- LayoutContainer.SetAnchorBottom(this, 0.8f);
- LayoutContainer.SetMarginLeft(this, 13);
- LayoutContainer.SetMarginTop(this, 110);
-
- HorizontalAlignment = HAlignment.Left;
- VerticalExpand = true;
-
- var resourceCache = IoCManager.Resolve();
-
- // everything needs to go within an inner panel container so the panel resizes to fit the elements.
- // Because ActionsUI is being anchored by layoutcontainer, the hotbar backing would appear too tall
- // if ActionsUI was the panel container
-
- var panelContainer = new PanelContainer()
- {
- StyleClasses = {StyleNano.StyleClassHotbarPanel},
- HorizontalAlignment = HAlignment.Left,
- VerticalAlignment = VAlignment.Top
- };
- AddChild(panelContainer);
-
- var hotbarContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- SeparationOverride = 3,
- HorizontalAlignment = HAlignment.Left
- };
- panelContainer.AddChild(hotbarContainer);
-
- var settingsContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- HorizontalExpand = true
- };
- hotbarContainer.AddChild(settingsContainer);
-
- settingsContainer.AddChild(new Control { HorizontalExpand = true, SizeFlagsStretchRatio = 1 });
- _lockTexture = resourceCache.GetTexture("/Textures/Interface/Nano/lock.svg.192dpi.png");
- _unlockTexture = resourceCache.GetTexture("/Textures/Interface/Nano/lock_open.svg.192dpi.png");
- _lockButton = new TextureButton
- {
- TextureNormal = _unlockTexture,
- HorizontalAlignment = HAlignment.Center,
- VerticalAlignment = VAlignment.Center,
- SizeFlagsStretchRatio = 1,
- Scale = (0.5f, 0.5f),
- ToolTip = Loc.GetString("ui-actionsui-function-lock-action-slots"),
- TooltipDelay = CustomTooltipDelay
- };
- settingsContainer.AddChild(_lockButton);
- settingsContainer.AddChild(new Control { HorizontalExpand = true, SizeFlagsStretchRatio = 2 });
- _settingsButton = new TextureButton
- {
- TextureNormal = resourceCache.GetTexture("/Textures/Interface/Nano/gear.svg.192dpi.png"),
- HorizontalAlignment = HAlignment.Center,
- VerticalAlignment = VAlignment.Center,
- SizeFlagsStretchRatio = 1,
- Scale = (0.5f, 0.5f),
- ToolTip = Loc.GetString("ui-actionsui-function-open-abilities-menu"),
- TooltipDelay = CustomTooltipDelay
- };
- settingsContainer.AddChild(_settingsButton);
- settingsContainer.AddChild(new Control { HorizontalExpand = true, SizeFlagsStretchRatio = 1 });
-
- // this allows a 2 column layout if window gets too small
- _slotContainer = new GridContainer
- {
- MaxGridHeight = CalcMaxHeight()
- };
- hotbarContainer.AddChild(_slotContainer);
-
- _loadoutContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- HorizontalExpand = true,
- MouseFilter = MouseFilterMode.Stop
- };
- hotbarContainer.AddChild(_loadoutContainer);
-
- _loadoutContainer.AddChild(new Control { HorizontalExpand = true, SizeFlagsStretchRatio = 1 });
- var previousHotbarIcon = new TextureRect()
- {
- Texture = resourceCache.GetTexture("/Textures/Interface/Nano/left_arrow.svg.192dpi.png"),
- HorizontalAlignment = HAlignment.Center,
- VerticalAlignment = VAlignment.Center,
- SizeFlagsStretchRatio = 1,
- TextureScale = (0.5f, 0.5f)
- };
- _loadoutContainer.AddChild(previousHotbarIcon);
- _loadoutContainer.AddChild(new Control { HorizontalExpand = true, SizeFlagsStretchRatio = 2 });
- _loadoutNumber = new Label
- {
- Text = "1",
- SizeFlagsStretchRatio = 1
- };
- _loadoutContainer.AddChild(_loadoutNumber);
- _loadoutContainer.AddChild(new Control { HorizontalExpand = true, SizeFlagsStretchRatio = 2 });
- var nextHotbarIcon = new TextureRect
- {
- Texture = resourceCache.GetTexture("/Textures/Interface/Nano/right_arrow.svg.192dpi.png"),
- HorizontalAlignment = HAlignment.Center,
- VerticalAlignment = VAlignment.Center,
- SizeFlagsStretchRatio = 1,
- TextureScale = (0.5f, 0.5f)
- };
- _loadoutContainer.AddChild(nextHotbarIcon);
- _loadoutContainer.AddChild(new Control { HorizontalExpand = true, SizeFlagsStretchRatio = 1 });
-
- _slots = new ActionSlot[ActionsSystem.Slots];
-
- _dragShadow = new TextureRect
- {
- MinSize = (64, 64),
- Stretch = TextureRect.StretchMode.Scale,
- Visible = false,
- SetSize = (64, 64)
- };
- UserInterfaceManager.PopupRoot.AddChild(_dragShadow);
-
- for (byte i = 0; i < ActionsSystem.Slots; i++)
- {
- var slot = new ActionSlot(this, _menu, i, _timing, _entMan);
- _slotContainer.AddChild(slot);
- _slots[i] = slot;
- }
-
- DragDropHelper = new DragDropHelper(OnBeginActionDrag, OnContinueActionDrag, OnEndActionDrag);
- DragDropHelper.Deadzone = DragDeadZone;
-
- MinSize = (10, 400);
- }
-
- protected override void EnteredTree()
- {
- base.EnteredTree();
- _lockButton.OnPressed += OnLockPressed;
- _settingsButton.OnPressed += OnToggleActionsMenu;
- _loadoutContainer.OnKeyBindDown += OnHotbarPaginate;
- _gameHud.ActionsButtonToggled += OnToggleActionsMenuTopButton;
- _gameHud.ActionsButtonDown = false;
- _gameHud.ActionsButtonVisible = true;
- }
-
- protected override void ExitedTree()
- {
- base.ExitedTree();
- StopTargeting();
- _menu.Close();
- _lockButton.OnPressed -= OnLockPressed;
- _settingsButton.OnPressed -= OnToggleActionsMenu;
- _loadoutContainer.OnKeyBindDown -= OnHotbarPaginate;
- _gameHud.ActionsButtonToggled -= OnToggleActionsMenuTopButton;
- _gameHud.ActionsButtonDown = false;
- _gameHud.ActionsButtonVisible = false;
- }
-
- protected override void Resized()
- {
- base.Resized();
- _slotContainer.MaxGridHeight = CalcMaxHeight();
- }
-
- private float CalcMaxHeight()
- {
- // TODO: Can rework this once https://github.com/space-wizards/RobustToolbox/issues/1392 is done,
- // this is here because there isn't currently a good way to allow the grid to adjust its height based
- // on constraints, otherwise we would use anchors to lay it out
-
- // it looks bad to have an uneven number of slots in the columns,
- // so we either do a single column or 2 equal sized columns
- if (Height < 650)
- {
- // 2 column
- return 400;
- }
- else
- {
- // 1 column
- return 900;
- }
- }
-
- protected override void UIScaleChanged()
- {
- _slotContainer.MaxGridHeight = CalcMaxHeight();
- base.UIScaleChanged();
- }
-
- ///
- /// Refresh the display of all the slots in the currently displayed hotbar,
- /// to reflect the current component state and assignments of actions component.
- ///
- public void UpdateUI()
- {
- _menu.UpdateUI();
-
- foreach (var actionSlot in Slots)
- {
- var action = System.Assignments[SelectedHotbar, actionSlot.SlotIndex];
-
- if (action == null)
- {
- if (SelectingTargetFor == actionSlot)
- StopTargeting(true);
- actionSlot.Clear();
- continue;
- }
-
- if (Component.Actions.TryGetValue(action, out var actualAction))
- {
- UpdateActionSlot(actualAction, actionSlot);
- continue;
- }
-
- // Action not in the actions component, but in the assignment list.
- // This is either an action that doesn't auto-clear from the menu, or the action menu was locked.
- // Show the old action, but make sure it is disabled;
- action.Enabled = false;
- action.Toggled = false;
-
- // If we enable the item-sprite, and if the item-sprite has a visual toggle, then the player will be
- // able to know whether the item is toggled, even if it is not in their LOS (but in PVS). And for things
- // like PDA sprites, the player can even see whether the action's item is currently inside of their PVS.
- // SO unless theres some way of "freezing" a sprite-view, we just have to disable it.
- action.ItemIconStyle = ItemActionIconStyle.NoItem;
-
- UpdateActionSlot(action, actionSlot);
- }
- }
-
- private void UpdateActionSlot(ActionType action, ActionSlot actionSlot)
- {
- actionSlot.Assign(action);
-
- if (!action.Enabled)
- {
- // just revoked an action we were trying to target with, stop targeting
- if (SelectingTargetFor?.Action != null && SelectingTargetFor.Action == action)
- {
- StopTargeting();
- }
-
- actionSlot.Disable();
- }
- else
- {
- actionSlot.Enable();
- }
-
- actionSlot.UpdateIcons();
- actionSlot.DrawModeChanged();
- }
-
- private void OnHotbarPaginate(GUIBoundKeyEventArgs args)
- {
- // rather than clicking the arrows themselves, the user can click the hbox so it's more
- // "forgiving" for misclicks, and we simply check which side they are closer to
- if (args.Function != EngineKeyFunctions.UIClick) return;
-
- var rightness = args.RelativePosition.X / _loadoutContainer.Width;
- if (rightness > 0.5)
- {
- ChangeHotbar((byte) ((SelectedHotbar + 1) % ActionsSystem.Hotbars));
- }
- else
- {
- var newBar = SelectedHotbar == 0 ? ActionsSystem.Hotbars - 1 : SelectedHotbar - 1;
- ChangeHotbar((byte) newBar);
- }
- }
-
- private void ChangeHotbar(byte hotbar)
- {
- StopTargeting();
- SelectedHotbar = hotbar;
- _loadoutNumber.Text = (hotbar + 1).ToString();
- UpdateUI();
- }
-
- ///
- /// If currently targeting with this slot, stops targeting.
- /// If currently targeting with no slot or a different slot, switches to
- /// targeting with the specified slot.
- ///
- ///
- public void ToggleTargeting(ActionSlot slot)
- {
- if (SelectingTargetFor == slot)
- {
- StopTargeting();
- return;
- }
- StartTargeting(slot);
- }
-
- ///
- /// Puts us in targeting mode, where we need to pick either a target point or entity
- ///
- private void StartTargeting(ActionSlot actionSlot)
- {
- if (actionSlot.Action == null)
- return;
-
- // If we were targeting something else we should stop
- StopTargeting();
-
- SelectingTargetFor = actionSlot;
-
- if (actionSlot.Action is TargetedAction targetAction)
- System.StartTargeting(targetAction);
-
- UpdateUI();
- }
-
- ///
- /// Switch out of targeting mode if currently selecting target for an action
- ///
- public void StopTargeting(bool updating = false)
- {
- if (SelectingTargetFor == null)
- return;
-
- SelectingTargetFor = null;
- System.StopTargeting();
-
- // Sometimes targeting gets stopped mid-UI update.
- // in that case, don't need to do a nested UI refresh.
- if (!updating)
- UpdateUI();
- }
-
- private void OnToggleActionsMenu(BaseButton.ButtonEventArgs args)
- {
- ToggleActionsMenu();
- }
-
- private void OnToggleActionsMenuTopButton(bool open)
- {
- if (open == _menu.IsOpen) return;
- ToggleActionsMenu();
- }
-
- public void ToggleActionsMenu()
- {
- if (_menu.IsOpen)
- {
- _menu.Close();
- }
- else
- {
- _menu.OpenCentered();
- }
- }
-
- private void OnLockPressed(BaseButton.ButtonEventArgs obj)
- {
- Locked = !Locked;
- _lockButton.TextureNormal = Locked ? _lockTexture : _unlockTexture;
- }
-
- private bool OnBeginActionDrag()
- {
- // only initiate the drag if the slot has an action in it
- if (Locked || DragDropHelper.Dragged?.Action == null) return false;
-
- _dragShadow.Texture = DragDropHelper.Dragged.Action.Icon?.Frame0();
- LayoutContainer.SetPosition(_dragShadow, UserInterfaceManager.MousePositionScaled.Position - (32, 32));
- DragDropHelper.Dragged.CancelPress();
- return true;
- }
-
- private bool OnContinueActionDrag(float frameTime)
- {
- // stop if there's no action in the slot
- if (Locked || DragDropHelper.Dragged?.Action == null) return false;
-
- // keep dragged entity centered under mouse
- LayoutContainer.SetPosition(_dragShadow, UserInterfaceManager.MousePositionScaled.Position - (32, 32));
- // we don't set this visible until frameupdate, otherwise it flickers
- _dragShadow.Visible = true;
- return true;
- }
-
- private void OnEndActionDrag()
- {
- _dragShadow.Visible = false;
- }
-
- ///
- /// Handle keydown / keyup for one of the slots via a keybinding, simulates mousedown/mouseup on it.
- ///
- /// slot index to to receive the press (0 corresponds to the one labeled 1, 9 corresponds to the one labeled 0)
- public void HandleHotbarKeybind(byte slot, PointerInputCmdHandler.PointerInputCmdArgs args)
- {
- var actionSlot = _slots[slot];
- actionSlot.Depress(args.State == BoundKeyState.Down);
- actionSlot.DrawModeChanged();
- }
-
- ///
- /// Handle hotbar change.
- ///
- /// hotbar index to switch to
- public void HandleChangeHotbarKeybind(byte hotbar, PointerInputCmdHandler.PointerInputCmdArgs args)
- {
- ChangeHotbar(hotbar);
- }
-
- protected override void FrameUpdate(FrameEventArgs args)
- {
- base.FrameUpdate(args);
- DragDropHelper.Update(args.DeltaSeconds);
- }
- }
-}
diff --git a/Content.Client/Administration/Systems/AdminSystem.Menu.cs b/Content.Client/Administration/Systems/AdminSystem.Menu.cs
deleted file mode 100644
index c93d0cad94..0000000000
--- a/Content.Client/Administration/Systems/AdminSystem.Menu.cs
+++ /dev/null
@@ -1,184 +0,0 @@
-using Content.Client.Administration.Managers;
-using Content.Client.Administration.UI;
-using Content.Client.Administration.UI.Tabs.ObjectsTab;
-using Content.Client.Administration.UI.Tabs.PlayerTab;
-using Content.Client.HUD;
-using Content.Client.Verbs;
-using Content.Shared.Input;
-using Robust.Client.Console;
-using Robust.Client.Graphics;
-using Robust.Client.Input;
-using Robust.Client.ResourceManagement;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
-using Robust.Shared.Input;
-using Robust.Shared.Input.Binding;
-using Robust.Shared.Network;
-
-namespace Content.Client.Administration.Systems
-{
- public sealed partial class AdminSystem
- {
- [Dependency] private readonly INetManager _netManager = default!;
- [Dependency] private readonly IInputManager _inputManager = default!;
- [Dependency] private readonly IGameHud _gameHud = default!;
- [Dependency] private readonly IClientAdminManager _clientAdminManager = default!;
- [Dependency] private readonly IClientConGroupController _clientConGroupController = default!;
- [Dependency] private readonly IClientConsoleHost _clientConsoleHost = default!;
-
- [Dependency] private readonly VerbSystem _verbSystem = default!;
-
- private AdminMenuWindow? _window;
- private readonly List _commandWindows = new();
-
- private void InitializeMenu()
- {
- // Reset the AdminMenu Window on disconnect
- _netManager.Disconnect += (_, _) => ResetWindow();
-
- _inputManager.SetInputCommand(ContentKeyFunctions.OpenAdminMenu,
- InputCmdHandler.FromDelegate(_ => Toggle()));
-
- _clientAdminManager.AdminStatusUpdated += () =>
- {
- // when status changes, show the top button if we can open admin menu.
- // if we can't or we lost admin status, close it and hide the button.
- _gameHud.AdminButtonVisible = CanOpen();
- if (!_gameHud.AdminButtonVisible)
- {
- Close();
- }
- };
- _gameHud.AdminButtonToggled += (open) =>
- {
- if (open)
- {
- TryOpen();
- }
- else
- {
- Close();
- }
- };
- _gameHud.AdminButtonVisible = CanOpen();
- _gameHud.AdminButtonDown = false;
- }
-
-
- public void ResetWindow()
- {
- _window?.Close();
- _window?.Dispose();
- _window = null;
-
- foreach (var window in _commandWindows)
- {
- window.Close();
- window.Dispose();
- }
-
- _commandWindows.Clear();
- }
-
- public void OpenCommand(BaseWindow window)
- {
- _commandWindows.Add(window);
- window.OpenCentered();
- }
-
- public void Open()
- {
- if (_window == null)
- {
- _window = new AdminMenuWindow();
- _window.OnClose += Close;
- }
-
- _window.PlayerTabControl.OnEntryPressed += PlayerTabEntryPressed;
- _window.ObjectsTabControl.OnEntryPressed += ObjectsTabEntryPressed;
- _window.OpenCentered();
- }
-
- public void Close()
- {
- if (_window != null)
- {
- _window.PlayerTabControl.OnEntryPressed -= PlayerTabEntryPressed;
- _window.ObjectsTabControl.OnEntryPressed -= ObjectsTabEntryPressed;
- }
-
- _window?.Close();
-
- foreach (var window in _commandWindows)
- window?.Dispose();
- _commandWindows.Clear();
- }
-
- ///
- /// Checks if the player can open the window
- ///
- /// True if the player is allowed
- public bool CanOpen()
- {
- return _clientConGroupController.CanAdminMenu();
- }
-
- ///
- /// Checks if the player can open the window and tries to open it
- ///
- public void TryOpen()
- {
- if (CanOpen())
- Open();
- }
-
- public void Toggle()
- {
- if (_window != null && _window.IsOpen)
- {
- Close();
- }
- else
- {
- TryOpen();
- }
- }
-
- private void PlayerTabEntryPressed(BaseButton.ButtonEventArgs args)
- {
- if (args.Button is not PlayerTabEntry button
- || button.PlayerUid == null)
- return;
-
- var uid = button.PlayerUid.Value;
- var function = args.Event.Function;
-
- if (function == EngineKeyFunctions.UIClick)
- _clientConsoleHost.ExecuteCommand($"vv {uid}");
- else if (function == EngineKeyFunctions.UseSecondary)
- _verbSystem.VerbMenu.OpenVerbMenu(uid, true);
- else
- return;
-
- args.Event.Handle();
- }
-
- private void ObjectsTabEntryPressed(BaseButton.ButtonEventArgs args)
- {
- if (args.Button is not ObjectsTabEntry button)
- return;
-
- var uid = button.AssocEntity;
- var function = args.Event.Function;
-
- if (function == EngineKeyFunctions.UIClick)
- _clientConsoleHost.ExecuteCommand($"vv {uid}");
- else if (function == EngineKeyFunctions.UseSecondary)
- _verbSystem.VerbMenu.OpenVerbMenu(uid, true);
- else
- return;
-
- args.Event.Handle();
- }
- }
-}
diff --git a/Content.Client/Administration/Systems/AdminSystem.cs b/Content.Client/Administration/Systems/AdminSystem.cs
index de62caa4ef..b353df680c 100644
--- a/Content.Client/Administration/Systems/AdminSystem.cs
+++ b/Content.Client/Administration/Systems/AdminSystem.cs
@@ -26,7 +26,6 @@ namespace Content.Client.Administration.Systems
base.Initialize();
InitializeOverlay();
- InitializeMenu();
SubscribeNetworkEvent(OnPlayerListChanged);
SubscribeNetworkEvent(OnPlayerInfoChanged);
SubscribeNetworkEvent(OnRoundRestartCleanup);
diff --git a/Content.Client/Administration/Systems/BwoinkSystem.cs b/Content.Client/Administration/Systems/BwoinkSystem.cs
index be3e2c0f31..a1d7a17dc9 100644
--- a/Content.Client/Administration/Systems/BwoinkSystem.cs
+++ b/Content.Client/Administration/Systems/BwoinkSystem.cs
@@ -1,150 +1,18 @@
#nullable enable
-using System.Diagnostics.CodeAnalysis;
-using Content.Client.Administration.Managers;
-using Content.Client.Administration.UI;
-using Content.Client.Administration.UI.CustomControls;
-using Content.Client.HUD;
using Content.Shared.Administration;
using JetBrains.Annotations;
-using Robust.Client.Graphics;
-using Robust.Client.Player;
-using Robust.Client.UserInterface.CustomControls;
-using Robust.Shared.Audio;
using Robust.Shared.Network;
-using Robust.Shared.Player;
namespace Content.Client.Administration.Systems
{
[UsedImplicitly]
public sealed class BwoinkSystem : SharedBwoinkSystem
{
- [Dependency] private readonly IClientAdminManager _adminManager = default!;
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IClyde _clyde = default!;
- [Dependency] private readonly IGameHud _hud = default!;
-
- private BwoinkWindow? _adminWindow;
- private DefaultWindow? _plainWindow;
- private readonly Dictionary _activePanelMap = new();
-
- public bool IsOpen => (_adminWindow?.IsOpen ?? false) || (_plainWindow?.IsOpen ?? false);
-
- public event Action? AdminReceivedAHelp;
- public event Action? AdminOpenedAHelp;
+ public event EventHandler? OnBwoinkTextMessageRecieved;
protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySessionEventArgs eventArgs)
{
- base.OnBwoinkTextMessage(message, eventArgs);
- LogBwoink(message);
- // Actual line
- var window = EnsurePanel(message.UserId);
- window.ReceiveLine(message);
- // Play a sound if we didn't send it
- var localPlayer = _playerManager.LocalPlayer;
- if (localPlayer?.UserId != message.TrueSender)
- {
- SoundSystem.Play("/Audio/Effects/adminhelp.ogg", Filter.Local());
- _clyde.RequestWindowAttention();
- }
-
- // If they're not an admin force it open so they read
- // If it's admin-admin messaging then eh.
- if (!_adminManager.HasFlag(AdminFlags.Adminhelp))
- _plainWindow?.Open();
- else
- {
- _adminWindow?.OnBwoink(message.UserId);
-
- if (_adminWindow?.IsOpen == true)
- return;
- AdminReceivedAHelp?.Invoke();
- _hud.SetInfoRed(true);
- }
- }
-
- public bool TryGetChannel(NetUserId ch, [NotNullWhen(true)] out BwoinkPanel? bp) => _activePanelMap.TryGetValue(ch, out bp);
-
- private BwoinkPanel EnsureAdmin(NetUserId channelId)
- {
- _adminWindow ??= new BwoinkWindow(this);
-
- if (!_activePanelMap.TryGetValue(channelId, out var existingPanel))
- {
- _activePanelMap[channelId] = existingPanel = new BwoinkPanel(this, channelId);
- existingPanel.Visible = false;
- if (!_adminWindow.BwoinkArea.Children.Contains(existingPanel))
- _adminWindow.BwoinkArea.AddChild(existingPanel);
- }
-
- return existingPanel;
- }
-
- private BwoinkPanel EnsurePlain(NetUserId channelId)
- {
- BwoinkPanel bp;
- if (_plainWindow is null)
- {
- bp = new BwoinkPanel(this, channelId);
- _plainWindow = new DefaultWindow()
- {
- TitleClass="windowTitleAlert",
- HeaderClass="windowHeaderAlert",
- Title=Loc.GetString("bwoink-user-title"),
- SetSize=(400, 200),
- };
-
- _plainWindow.Contents.AddChild(bp);
- }
- else
- {
- bp = (BwoinkPanel) _plainWindow.Contents.GetChild(0);
- }
-
- return bp;
- }
-
- public BwoinkPanel EnsurePanel(NetUserId channelId)
- {
- if (_adminManager.HasFlag(AdminFlags.Adminhelp))
- return EnsureAdmin(channelId);
-
- return EnsurePlain(channelId);
- }
-
- public void Open(NetUserId? channelId = null)
- {
- if (channelId == null)
- {
- var localPlayer = _playerManager.LocalPlayer;
- if (localPlayer != null)
- Open(localPlayer.UserId);
- return;
- }
-
- _hud.SetInfoRed(false);
- AdminOpenedAHelp?.Invoke();
-
- if (_adminManager.HasFlag(AdminFlags.Adminhelp))
- {
- SelectChannel(channelId.Value);
- _adminWindow?.Open();
- return;
- }
-
- EnsurePlain(channelId.Value);
- _plainWindow?.Open();
- }
-
- public void Close()
- {
- _adminWindow?.Close();
- _plainWindow?.Close();
- }
-
- private void SelectChannel(NetUserId uid)
- {
- _adminWindow ??= new BwoinkWindow(this);
- _adminWindow.SelectChannel(uid);
+ OnBwoinkTextMessageRecieved?.Invoke(this, message);
}
public void Send(NetUserId channelId, string text)
diff --git a/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs b/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
index 716d1d6e6a..385dbb4cee 100644
--- a/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
+++ b/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
@@ -1,18 +1,12 @@
-using Content.Client.Administration.UI.Tabs;
-using Content.Client.HUD;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
namespace Content.Client.Administration.UI
{
[GenerateTypedNameReferences]
public sealed partial class AdminMenuWindow : DefaultWindow
{
- [Dependency] private readonly IGameHud? _gameHud = default!;
-
public AdminMenuWindow()
{
MinSize = (500, 250);
@@ -27,19 +21,5 @@ namespace Content.Client.Administration.UI
MasterTabContainer.SetTabTitle(5, Loc.GetString("admin-menu-players-tab"));
MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-objects-tab"));
}
-
- protected override void EnteredTree()
- {
- base.EnteredTree();
- if (_gameHud != null)
- _gameHud.AdminButtonDown = true;
- }
-
- protected override void ExitedTree()
- {
- base.ExitedTree();
- if (_gameHud != null)
- _gameHud.AdminButtonDown = false;
- }
}
}
diff --git a/Content.Client/Administration/UI/BwoinkWindow.xaml.cs b/Content.Client/Administration/UI/BwoinkWindow.xaml.cs
index 101b3b38d3..eb1fbd4030 100644
--- a/Content.Client/Administration/UI/BwoinkWindow.xaml.cs
+++ b/Content.Client/Administration/UI/BwoinkWindow.xaml.cs
@@ -6,6 +6,7 @@ using Content.Client.Administration.Systems;
using Content.Client.Administration.UI.CustomControls;
using Content.Client.Administration.UI.Tabs.AdminTab;
using Content.Client.Stylesheets;
+using Content.Client.UserInterface.Systems.Bwoink;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
@@ -27,15 +28,16 @@ namespace Content.Client.Administration.UI
{
[Dependency] private readonly IClientAdminManager _adminManager = default!;
[Dependency] private readonly IClientConsoleHost _console = default!;
+ private readonly AdminAHelpUIHandler _adminAHelpHelper;
- private readonly BwoinkSystem _bwoinkSystem;
+ //private readonly BwoinkSystem _bwoinkSystem;
private PlayerInfo? _currentPlayer = default;
- public BwoinkWindow(BwoinkSystem bs)
+ public BwoinkWindow(AdminAHelpUIHandler adminAHelpHelper)
{
+ _adminAHelpHelper = adminAHelpHelper;
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
- _bwoinkSystem = bs;
_adminManager.AdminStatusUpdated += FixButtons;
FixButtons();
@@ -57,7 +59,7 @@ namespace Content.Client.Administration.UI
var sb = new StringBuilder();
sb.Append(info.Connected ? '●' : '○');
sb.Append(' ');
- if (_bwoinkSystem.TryGetChannel(info.SessionId, out var panel) && panel.Unread > 0)
+ if (_adminAHelpHelper.TryGetChannel(info.SessionId, out var panel) && panel.Unread > 0)
{
if (panel.Unread < 11)
sb.Append(new Rune('➀' + (panel.Unread-1)));
@@ -76,8 +78,8 @@ namespace Content.Client.Administration.UI
ChannelSelector.Comparison = (a, b) =>
{
- var aChannelExists = _bwoinkSystem.TryGetChannel(a.SessionId, out var ach);
- var bChannelExists = _bwoinkSystem.TryGetChannel(b.SessionId, out var bch);
+ var aChannelExists = _adminAHelpHelper.TryGetChannel(a.SessionId, out var ach);
+ var bChannelExists = _adminAHelpHelper.TryGetChannel(b.SessionId, out var bch);
if (!aChannelExists && !bChannelExists)
return 0;
@@ -174,7 +176,7 @@ namespace Content.Client.Administration.UI
var sb = new StringBuilder();
sb.Append(pl.Connected ? '●' : '○');
sb.Append(' ');
- if (_bwoinkSystem.TryGetChannel(pl.SessionId, out var panel) && panel.Unread > 0)
+ if (_adminAHelpHelper.TryGetChannel(pl.SessionId, out var panel) && panel.Unread > 0)
{
if (panel.Unread < 11)
sb.Append(new Rune('➀' + (panel.Unread-1)));
@@ -200,7 +202,7 @@ namespace Content.Client.Administration.UI
{
foreach (var bw in BwoinkArea.Children)
bw.Visible = false;
- var panel = _bwoinkSystem.EnsurePanel(ch);
+ var panel = _adminAHelpHelper.EnsurePanel(ch);
panel.Visible = true;
}
diff --git a/Content.Client/Administration/UI/CustomControls/BwoinkPanel.xaml.cs b/Content.Client/Administration/UI/CustomControls/BwoinkPanel.xaml.cs
index 70714b9247..16edcc1270 100644
--- a/Content.Client/Administration/UI/CustomControls/BwoinkPanel.xaml.cs
+++ b/Content.Client/Administration/UI/CustomControls/BwoinkPanel.xaml.cs
@@ -1,6 +1,7 @@
#nullable enable
using System;
using Content.Client.Administration.Systems;
+using Content.Client.UserInterface.Systems.Bwoink;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
@@ -13,17 +14,15 @@ namespace Content.Client.Administration.UI.CustomControls
[GenerateTypedNameReferences]
public sealed partial class BwoinkPanel : BoxContainer
{
- private readonly BwoinkSystem _bwoinkSystem;
- public readonly NetUserId ChannelId;
+ private readonly Action _messageSender;
public int Unread { get; private set; } = 0;
public DateTime LastMessage { get; private set; } = DateTime.MinValue;
- public BwoinkPanel(BwoinkSystem bwoinkSys, NetUserId userId)
+ public BwoinkPanel(Action messageSender)
{
RobustXamlLoader.Load(this);
- _bwoinkSystem = bwoinkSys;
- ChannelId = userId;
+ _messageSender = messageSender;
OnVisibilityChanged += c =>
{
@@ -38,7 +37,7 @@ namespace Content.Client.Administration.UI.CustomControls
if (string.IsNullOrWhiteSpace(args.Text))
return;
- _bwoinkSystem.Send(ChannelId, args.Text);
+ _messageSender.Invoke(args.Text);
SenderLineEdit.Clear();
}
diff --git a/Content.Client/Alerts/UI/AlertsUI.xaml b/Content.Client/Alerts/UI/AlertsUI.xaml
deleted file mode 100644
index eac250bdac..0000000000
--- a/Content.Client/Alerts/UI/AlertsUI.xaml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
diff --git a/Content.Client/Alerts/UI/AlertsUI.xaml.cs b/Content.Client/Alerts/UI/AlertsUI.xaml.cs
deleted file mode 100644
index e23f2fd140..0000000000
--- a/Content.Client/Alerts/UI/AlertsUI.xaml.cs
+++ /dev/null
@@ -1,344 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Content.Client.Chat.Managers;
-using Content.Client.Chat.UI;
-using Content.Shared.Alert;
-using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Shared.GameObjects;
-using Robust.Shared.Input;
-using Robust.Shared.IoC;
-using Robust.Shared.Log;
-
-namespace Content.Client.Alerts.UI;
-
-public sealed class AlertsFramePresenter : IDisposable
-{
- [Dependency] private readonly IEntitySystemManager _systemManager = default!;
- [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
- [Dependency] private readonly IChatManager _chatManager = default!;
-
- private IAlertsFrameView _alertsFrame;
- private ClientAlertsSystem? _alertsSystem;
-
- public AlertsFramePresenter()
- {
- // This is a lot easier than a factory
- IoCManager.InjectDependencies(this);
-
- _alertsFrame = new AlertsUI(_chatManager);
- _userInterfaceManager.StateRoot.AddChild((AlertsUI) _alertsFrame);
-
- // This is required so that if we load after the system is initialized, we can bind to it immediately
- if (_systemManager.TryGetEntitySystem(out var alertsSystem))
- SystemBindingChanged(alertsSystem);
-
- _systemManager.SystemLoaded += OnSystemLoaded;
- _systemManager.SystemUnloaded += OnSystemUnloaded;
-
- _alertsFrame.AlertPressed += OnAlertPressed;
-
- // initially populate the frame if system is available
- var alerts = alertsSystem?.ActiveAlerts;
- if (alerts != null)
- {
- SystemOnSyncAlerts(alertsSystem, alerts);
- }
- }
-
- ///
- public void Dispose()
- {
- _userInterfaceManager.StateRoot.RemoveChild((AlertsUI) _alertsFrame);
- _alertsFrame.Dispose();
- _alertsFrame = null!;
-
- SystemBindingChanged(null);
- _systemManager.SystemLoaded -= OnSystemLoaded;
- _systemManager.SystemUnloaded -= OnSystemUnloaded;
- }
-
- private void OnAlertPressed(object? sender, AlertType e)
- {
- _alertsSystem?.AlertClicked(e);
- }
-
- private void SystemOnClearAlerts(object? sender, EventArgs e)
- {
- _alertsFrame.ClearAllControls();
- }
-
- private void SystemOnSyncAlerts(object? sender, IReadOnlyDictionary e)
- {
- if (sender is ClientAlertsSystem system)
- _alertsFrame.SyncControls(system, system.AlertOrder, e);
- }
-
- //TODO: This system binding boilerplate seems to be duplicated between every presenter
- // prob want to pull it out into a generic object with callbacks for Onbind/OnUnbind
- #region System Binding
-
- private void OnSystemLoaded(object? sender, SystemChangedArgs args)
- {
- if (args.System is ClientAlertsSystem system) SystemBindingChanged(system);
- }
-
- private void OnSystemUnloaded(object? sender, SystemChangedArgs args)
- {
- if (args.System is ClientAlertsSystem) SystemBindingChanged(null);
- }
-
- private void SystemBindingChanged(ClientAlertsSystem? newSystem)
- {
- if (newSystem is null)
- {
- if (_alertsSystem is null)
- return;
-
- UnbindFromSystem();
- }
- else
- {
- if (_alertsSystem is null)
- {
- BindToSystem(newSystem);
- return;
- }
-
- UnbindFromSystem();
- BindToSystem(newSystem);
- }
- }
-
- private void BindToSystem(ClientAlertsSystem system)
- {
- _alertsSystem = system;
- system.SyncAlerts += SystemOnSyncAlerts;
- system.ClearAlerts += SystemOnClearAlerts;
- }
-
- private void UnbindFromSystem()
- {
- var system = _alertsSystem;
-
- if (system is null)
- throw new InvalidOperationException();
-
- system.SyncAlerts -= SystemOnSyncAlerts;
- system.ClearAlerts -= SystemOnClearAlerts;
- }
-
- #endregion
-}
-
-///
-/// This is the frame of vertical set of alerts that show up on the HUD.
-///
-public interface IAlertsFrameView : IDisposable
-{
- event EventHandler? AlertPressed;
-
- void SyncControls(AlertsSystem alertsSystem, AlertOrderPrototype? alertOrderPrototype,
- IReadOnlyDictionary alertStates);
- void ClearAllControls();
-}
-
-///
-/// The status effects display on the right side of the screen.
-///
-[GenerateTypedNameReferences]
-public sealed partial class AlertsUI : Control, IAlertsFrameView
-{
- // also known as Control.Children?
- private readonly Dictionary _alertControls = new();
-
- public AlertsUI(IChatManager chatManager)
- {
- _chatManager = chatManager;
- RobustXamlLoader.Load(this);
-
- LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin);
- LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.End);
- LayoutContainer.SetAnchorTop(this, 0f);
- LayoutContainer.SetAnchorRight(this, 1f);
- LayoutContainer.SetAnchorBottom(this, 1f);
- LayoutContainer.SetMarginBottom(this, -180);
- LayoutContainer.SetMarginTop(this, 250);
- LayoutContainer.SetMarginRight(this, -10);
- }
-
- public void SyncControls(AlertsSystem alertsSystem, AlertOrderPrototype? alertOrderPrototype,
- IReadOnlyDictionary alertStates)
- {
- // remove any controls with keys no longer present
- if (SyncRemoveControls(alertStates)) return;
-
- // now we know that alertControls contains alerts that should still exist but
- // may need to updated,
- // also there may be some new alerts we need to show.
- // further, we need to ensure they are ordered w.r.t their configured order
- SyncUpdateControls(alertsSystem, alertOrderPrototype, alertStates);
- }
-
- public void ClearAllControls()
- {
- foreach (var alertControl in _alertControls.Values)
- {
- alertControl.OnPressed -= AlertControlPressed;
- alertControl.Dispose();
- }
-
- _alertControls.Clear();
- }
-
- public event EventHandler? AlertPressed;
-
- //TODO: This control caring about it's layout relative to other controls in the tree is terrible
- // the presenters or gamescreen should be dealing with this
- // probably want to tackle this after chatbox gets MVP'd
- #region Spaghetti
-
- public const float ChatSeparation = 38f;
- private readonly IChatManager _chatManager;
-
- protected override void EnteredTree()
- {
- base.EnteredTree();
- _chatManager.OnChatBoxResized += OnChatResized;
- OnChatResized(new ChatResizedEventArgs(HudChatBox.InitialChatBottom));
- }
-
- protected override void ExitedTree()
- {
- base.ExitedTree();
- _chatManager.OnChatBoxResized -= OnChatResized;
- }
-
- private void OnChatResized(ChatResizedEventArgs chatResizedEventArgs)
- {
- // resize us to fit just below the chat box
- if (_chatManager.CurrentChatBox != null)
- LayoutContainer.SetMarginTop(this, chatResizedEventArgs.NewBottom + ChatSeparation);
- else
- LayoutContainer.SetMarginTop(this, 250);
- }
-
- #endregion
-
- // This makes no sense but I'm leaving it in place in case I break anything by removing it.
- protected override void Resized()
- {
- // TODO: Can rework this once https://github.com/space-wizards/RobustToolbox/issues/1392 is done,
- // this is here because there isn't currently a good way to allow the grid to adjust its height based
- // on constraints, otherwise we would use anchors to lay it out
- base.Resized();
- AlertContainer.MaxGridHeight = Height;
- }
-
- protected override void UIScaleChanged()
- {
- AlertContainer.MaxGridHeight = Height;
- base.UIScaleChanged();
- }
-
- private bool SyncRemoveControls(IReadOnlyDictionary alertStates)
- {
- var toRemove = new List();
- foreach (var existingKey in _alertControls.Keys)
- {
- if (!alertStates.ContainsKey(existingKey)) toRemove.Add(existingKey);
- }
-
- foreach (var alertKeyToRemove in toRemove)
- {
- _alertControls.Remove(alertKeyToRemove, out var control);
- if (control == null) return true;
- AlertContainer.Children.Remove(control);
- }
-
- return false;
- }
-
- private void SyncUpdateControls(AlertsSystem alertsSystem, AlertOrderPrototype? alertOrderPrototype,
- IReadOnlyDictionary alertStates)
- {
- foreach (var (alertKey, alertState) in alertStates)
- {
- if (!alertKey.AlertType.HasValue)
- {
- Logger.WarningS("alert", "found alertkey without alerttype," +
- " alert keys should never be stored without an alerttype set: {0}", alertKey);
- continue;
- }
-
- var alertType = alertKey.AlertType.Value;
- if (!alertsSystem.TryGet(alertType, out var newAlert))
- {
- Logger.ErrorS("alert", "Unrecognized alertType {0}", alertType);
- continue;
- }
-
- if (_alertControls.TryGetValue(newAlert.AlertKey, out var existingAlertControl) &&
- existingAlertControl.Alert.AlertType == newAlert.AlertType)
- {
- // key is the same, simply update the existing control severity / cooldown
- existingAlertControl.SetSeverity(alertState.Severity);
- existingAlertControl.Cooldown = alertState.Cooldown;
- }
- else
- {
- if (existingAlertControl != null) AlertContainer.Children.Remove(existingAlertControl);
-
- // this is a new alert + alert key or just a different alert with the same
- // key, create the control and add it in the appropriate order
- var newAlertControl = CreateAlertControl(newAlert, alertState);
-
- //TODO: Can the presenter sort the states before giving it to us?
- if (alertOrderPrototype != null)
- {
- var added = false;
- foreach (var alertControl in AlertContainer.Children)
- {
- if (alertOrderPrototype.Compare(newAlert, ((AlertControl) alertControl).Alert) >= 0)
- continue;
-
- var idx = alertControl.GetPositionInParent();
- AlertContainer.Children.Add(newAlertControl);
- newAlertControl.SetPositionInParent(idx);
- added = true;
- break;
- }
-
- if (!added) AlertContainer.Children.Add(newAlertControl);
- }
- else
- AlertContainer.Children.Add(newAlertControl);
-
- _alertControls[newAlert.AlertKey] = newAlertControl;
- }
- }
- }
-
- private AlertControl CreateAlertControl(AlertPrototype alert, AlertState alertState)
- {
- var alertControl = new AlertControl(alert, alertState.Severity)
- {
- Cooldown = alertState.Cooldown
- };
- alertControl.OnPressed += AlertControlPressed;
- return alertControl;
- }
-
- private void AlertControlPressed(BaseButton.ButtonEventArgs args)
- {
- if (args.Button is not AlertControl control)
- return;
-
- if (args.Event.Function != EngineKeyFunctions.UIClick)
- return;
-
- AlertPressed?.Invoke(this, control.Alert.AlertType);
- }
-}
diff --git a/Content.Client/Changelog/ChangelogButton.cs b/Content.Client/Changelog/ChangelogButton.cs
index b029770857..6e8862492c 100644
--- a/Content.Client/Changelog/ChangelogButton.cs
+++ b/Content.Client/Changelog/ChangelogButton.cs
@@ -1,6 +1,4 @@
using Content.Client.Stylesheets;
-using Content.Client.UserInterface.Systems.EscapeMenu;
-using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.Changelog
@@ -13,8 +11,6 @@ namespace Content.Client.Changelog
{
IoCManager.InjectDependencies(this);
- OnPressed += OnChangelogPressed;
-
// So that measuring before opening returns a correct height,
// and the window has the correct size when opened.
Text = " ";
@@ -35,11 +31,6 @@ namespace Content.Client.Changelog
_changelogManager.NewChangelogEntriesChanged -= UpdateStuff;
}
- private void OnChangelogPressed(ButtonEventArgs obj)
- {
- IoCManager.Resolve().GetUIController().ToggleWindow();
- }
-
private void UpdateStuff()
{
if (_changelogManager.NewChangelogEntries)
diff --git a/Content.Client/Changelog/ChangelogWindow.xaml.cs b/Content.Client/Changelog/ChangelogWindow.xaml.cs
index f666693772..1cb8377c99 100644
--- a/Content.Client/Changelog/ChangelogWindow.xaml.cs
+++ b/Content.Client/Changelog/ChangelogWindow.xaml.cs
@@ -205,7 +205,7 @@ namespace Content.Client.Changelog
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
- IoCManager.Resolve().GetUIController().ToggleWindow();
+ IoCManager.Resolve().GetUIController().OpenWindow();
}
}
}
diff --git a/Content.Client/CharacterInfo/CharacterInfoSystem.cs b/Content.Client/CharacterInfo/CharacterInfoSystem.cs
new file mode 100644
index 0000000000..7257c230ba
--- /dev/null
+++ b/Content.Client/CharacterInfo/CharacterInfoSystem.cs
@@ -0,0 +1,58 @@
+using Content.Shared.CharacterInfo;
+using Content.Shared.Objectives;
+using Robust.Client.GameObjects;
+using Robust.Client.Player;
+
+namespace Content.Client.CharacterInfo;
+
+public sealed class CharacterInfoSystem : EntitySystem
+{
+ [Dependency] private readonly IPlayerManager _players = default!;
+
+ public event Action? OnCharacterUpdate;
+ public event Action? OnCharacterDetached;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnPlayerAttached);
+
+ SubscribeNetworkEvent(OnCharacterInfoEvent);
+ }
+
+ public void RequestCharacterInfo()
+ {
+ var entity = _players.LocalPlayer?.ControlledEntity;
+ if (entity == null)
+ {
+ return;
+ }
+
+ RaiseNetworkEvent(new RequestCharacterInfoEvent(entity.Value));
+ }
+
+ private void OnPlayerAttached(PlayerAttachSysMessage msg)
+ {
+ if (msg.AttachedEntity == default)
+ {
+ OnCharacterDetached?.Invoke();
+ }
+ }
+
+ private void OnCharacterInfoEvent(CharacterInfoEvent msg, EntitySessionEventArgs args)
+ {
+ var sprite = CompOrNull(msg.EntityUid);
+ var data = new CharacterData(msg.JobTitle, msg.Objectives, msg.Briefing, sprite, Name(msg.EntityUid));
+
+ OnCharacterUpdate?.Invoke(data);
+ }
+
+ public readonly record struct CharacterData(
+ string Job,
+ Dictionary> Objectives,
+ string Briefing,
+ ISpriteComponent? Sprite,
+ string EntityName
+ );
+}
diff --git a/Content.Client/CharacterInfo/Components/CharacterInfoComponent.cs b/Content.Client/CharacterInfo/Components/CharacterInfoComponent.cs
deleted file mode 100644
index a5b52b7c82..0000000000
--- a/Content.Client/CharacterInfo/Components/CharacterInfoComponent.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-using Content.Client.CharacterInterface;
-using Content.Client.Stylesheets;
-using Content.Client.UserInterface.Controls;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-
-namespace Content.Client.CharacterInfo.Components
-{
- [RegisterComponent]
- public sealed class CharacterInfoComponent : Component, ICharacterUI
- {
- public CharacterInfoControl Control = default!;
-
- public Control Scene { get; set; } = default!;
- public UIPriority Priority => UIPriority.Info;
-
- public void Opened()
- {
- IoCManager.Resolve().GetEntitySystem().RequestCharacterInfo(Owner);
- }
-
- public sealed class CharacterInfoControl : BoxContainer
- {
- public SpriteView SpriteView { get; }
- public Label NameLabel { get; }
- public Label SubText { get; }
-
- public BoxContainer ObjectivesContainer { get; }
-
- public CharacterInfoControl()
- {
- IoCManager.InjectDependencies(this);
-
- Orientation = LayoutOrientation.Vertical;
-
- AddChild(new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- Children =
- {
- (SpriteView = new SpriteView { OverrideDirection = Direction.South, Scale = (2,2)}),
- new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- VerticalAlignment = VAlignment.Top,
- Children =
- {
- (NameLabel = new Label()),
- (SubText = new Label
- {
- VerticalAlignment = VAlignment.Top,
- StyleClasses = {StyleBase.StyleClassLabelSubText},
-
- })
- }
- }
- }
- });
-
- AddChild(new Label
- {
- Text = Loc.GetString("character-info-objectives-label"),
- HorizontalAlignment = HAlignment.Center
- });
- ObjectivesContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical
- };
- AddChild(ObjectivesContainer);
-
- AddChild(new Placeholder
- {
- PlaceholderText = Loc.GetString("character-info-roles-antagonist-text")
- });
- }
- }
- }
-}
diff --git a/Content.Client/CharacterInfo/Components/CharacterInfoSystem.cs b/Content.Client/CharacterInfo/Components/CharacterInfoSystem.cs
deleted file mode 100644
index 1423ef0cdb..0000000000
--- a/Content.Client/CharacterInfo/Components/CharacterInfoSystem.cs
+++ /dev/null
@@ -1,111 +0,0 @@
-using Content.Client.UserInterface.Controls;
-using Content.Shared.CharacterInfo;
-using Content.Shared.Objectives;
-using Robust.Client.GameObjects;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.Utility;
-
-namespace Content.Client.CharacterInfo.Components;
-
-public sealed class CharacterInfoSystem : EntitySystem
-{
- [Dependency] private readonly SpriteSystem _sprite = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeNetworkEvent(OnCharacterInfoEvent);
- SubscribeLocalEvent(OnComponentAdd);
- }
-
- private void OnComponentAdd(EntityUid uid, CharacterInfoComponent component, ComponentAdd args)
- {
- component.Scene = component.Control = new CharacterInfoComponent.CharacterInfoControl();
- }
-
- public void RequestCharacterInfo(EntityUid entityUid)
- {
- RaiseNetworkEvent(new RequestCharacterInfoEvent(entityUid));
- }
-
- private void OnCharacterInfoEvent(CharacterInfoEvent msg, EntitySessionEventArgs args)
- {
- if (!EntityManager.TryGetComponent(msg.EntityUid, out CharacterInfoComponent? characterInfoComponent))
- return;
-
- UpdateUI(characterInfoComponent, msg.JobTitle, msg.Objectives, msg.Briefing);
- if (EntityManager.TryGetComponent(msg.EntityUid, out ISpriteComponent? spriteComponent))
- {
- characterInfoComponent.Control.SpriteView.Sprite = spriteComponent;
- }
-
- if (!EntityManager.TryGetComponent(msg.EntityUid, out MetaDataComponent? metadata))
- return;
- characterInfoComponent.Control.NameLabel.Text = metadata.EntityName;
- }
-
- private void UpdateUI(CharacterInfoComponent comp, string jobTitle, Dictionary> objectives, string briefing)
- {
- comp.Control.SubText.Text = jobTitle;
-
- comp.Control.ObjectivesContainer.RemoveAllChildren();
- foreach (var (groupId, objectiveConditions) in objectives)
- {
- var vbox = new BoxContainer
- {
- Orientation = BoxContainer.LayoutOrientation.Vertical,
- Modulate = Color.Gray
- };
-
- vbox.AddChild(new Label
- {
- Text = groupId,
- Modulate = Color.LightSkyBlue
- });
-
- foreach (var objectiveCondition in objectiveConditions)
- {
- var hbox = new BoxContainer
- {
- Orientation = BoxContainer.LayoutOrientation.Horizontal
- };
- hbox.AddChild(new ProgressTextureRect
- {
- Texture = _sprite.Frame0(objectiveCondition.SpriteSpecifier),
- Progress = objectiveCondition.Progress,
- VerticalAlignment = Control.VAlignment.Center
- });
- hbox.AddChild(new Control
- {
- MinSize = (10,0)
- });
- hbox.AddChild(new BoxContainer
- {
- Orientation = BoxContainer.LayoutOrientation.Vertical,
- Children =
- {
- new Label{Text = objectiveCondition.Title},
- new Label{Text = objectiveCondition.Description}
- }
- }
- );
- vbox.AddChild(hbox);
- }
- var briefinghBox = new BoxContainer
- {
- Orientation = BoxContainer.LayoutOrientation.Horizontal
- };
-
- briefinghBox.AddChild(new Label
- {
- Text = briefing,
- Modulate = Color.Yellow
- });
-
- vbox.AddChild(briefinghBox);
- comp.Control.ObjectivesContainer.AddChild(vbox);
- }
- }
-}
diff --git a/Content.Client/CharacterInfo/Components/ICharacterUI.cs b/Content.Client/CharacterInfo/Components/ICharacterUI.cs
deleted file mode 100644
index f71f1b0ca4..0000000000
--- a/Content.Client/CharacterInfo/Components/ICharacterUI.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Content.Client.CharacterInterface;
-using Robust.Client.UserInterface;
-
-namespace Content.Client.CharacterInfo.Components
-{
- ///
- /// An interface which is gathered to assemble the character window from multiple components
- ///
- public interface ICharacterUI
- {
- ///
- /// The control which holds the character user interface to be included in the window
- ///
- Control Scene { get; }
-
- ///
- /// The order it will appear in the character UI, higher is lower
- ///
- UIPriority Priority { get; }
-
- ///
- /// Called when the CharacterUi was opened
- ///
- void Opened(){}
- }
-}
diff --git a/Content.Client/CharacterInterface/CharacterInterfaceComponent.cs b/Content.Client/CharacterInterface/CharacterInterfaceComponent.cs
deleted file mode 100644
index 1d30edb9e0..0000000000
--- a/Content.Client/CharacterInterface/CharacterInterfaceComponent.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-using System.Collections.Generic;
-using Content.Client.CharacterInfo.Components;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
-using Robust.Shared.GameObjects;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
-
-namespace Content.Client.CharacterInterface
-{
- ///
- /// A semi-abstract component which gets added to entities upon attachment and collects all character
- /// user interfaces into a single window and keybind for the user
- ///
- [RegisterComponent]
- public sealed class CharacterInterfaceComponent : Component
- {
- ///
- /// Window to hold each of the character interfaces
- ///
- ///
- /// Null if it would otherwise be empty.
- ///
- public CharacterWindow? Window { get; set; }
-
- public List? UIComponents;
-
- ///
- /// A window that collects and shows all the individual character user interfaces
- ///
- public sealed class CharacterWindow : DefaultWindow
- {
- private readonly List _windowComponents;
-
- public CharacterWindow(List windowComponents)
- {
- Title = Loc.GetString("character-info-title");
-
- var contentsVBox = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical
- };
-
- var mainScrollContainer = new ScrollContainer { };
- mainScrollContainer.AddChild(contentsVBox);
-
- Contents.AddChild(mainScrollContainer);
-
- windowComponents.Sort((a, b) => ((int) a.Priority).CompareTo((int) b.Priority));
- foreach (var element in windowComponents)
- {
- contentsVBox.AddChild(element.Scene);
- }
-
- _windowComponents = windowComponents;
- }
-
- protected override void Opened()
- {
- base.Opened();
- foreach (var windowComponent in _windowComponents)
- {
- windowComponent.Opened();
- }
- }
- }
- }
-
- ///
- /// Determines ordering of the character user interface, small values come sooner
- ///
- public enum UIPriority
- {
- First = 0,
- Info = 5,
- Species = 100,
- Last = 99999
- }
-}
diff --git a/Content.Client/CharacterInterface/CharacterInterfaceSystem.cs b/Content.Client/CharacterInterface/CharacterInterfaceSystem.cs
deleted file mode 100644
index 09a17e7459..0000000000
--- a/Content.Client/CharacterInterface/CharacterInterfaceSystem.cs
+++ /dev/null
@@ -1,140 +0,0 @@
-using System.Linq;
-using Content.Client.CharacterInfo.Components;
-using Content.Client.HUD;
-using Content.Shared.Input;
-using JetBrains.Annotations;
-using Robust.Client.GameObjects;
-using Robust.Client.Input;
-using Robust.Client.Player;
-using Robust.Client.UserInterface.CustomControls;
-using Robust.Shared.GameObjects;
-using Robust.Shared.Input.Binding;
-using Robust.Shared.IoC;
-
-namespace Content.Client.CharacterInterface
-{
- [UsedImplicitly]
- public sealed class CharacterInterfaceSystem : EntitySystem
- {
- [Dependency] private readonly IGameHud _gameHud = default!;
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IInputManager _inputManager = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- CommandBinds.Builder
- .Bind(ContentKeyFunctions.OpenCharacterMenu,
- InputCmdHandler.FromDelegate(_ => HandleOpenCharacterMenu()))
- .Register();
-
- SubscribeLocalEvent(OnComponentInit);
- SubscribeLocalEvent(OnComponentRemove);
- SubscribeLocalEvent(OnPlayerAttached);
- SubscribeLocalEvent(OnPlayerDetached);
- }
-
- public override void Shutdown()
- {
- CommandBinds.Unregister();
- base.Shutdown();
- }
-
- private void OnComponentInit(EntityUid uid, CharacterInterfaceComponent comp, ComponentInit args)
- {
- //Use all the character ui interfaced components to create the character window
- comp.UIComponents = EntityManager.GetComponents(uid).ToList();
- if (comp.UIComponents.Count == 0)
- return;
-
- comp.Window = new CharacterInterfaceComponent.CharacterWindow(comp.UIComponents)
- {
- SetSize = (545, 400)
- };
-
- comp.Window.OnClose += () => _gameHud.CharacterButtonDown = false;
- }
-
- private void OnComponentRemove(EntityUid uid, CharacterInterfaceComponent comp, ComponentRemove args)
- {
- if (comp.UIComponents != null)
- {
- foreach (var component in comp.UIComponents)
- {
- // Make sure these don't get deleted when the window is disposed.
- component.Scene.Orphan();
- }
- }
-
- comp.UIComponents = null;
-
- comp.Window?.Close();
- comp.Window = null;
-
- _inputManager.SetInputCommand(ContentKeyFunctions.OpenCharacterMenu, null);
- }
-
- private void OnPlayerAttached(EntityUid uid, CharacterInterfaceComponent comp, PlayerAttachedEvent args)
- {
- if (comp.Window == null)
- return;
-
- _gameHud.CharacterButtonVisible = true;
- _gameHud.CharacterButtonToggled += ToggleWindow;
- }
-
- private void ToggleWindow(bool toggle)
- {
- if (!TryComp(_playerManager.LocalPlayer?.Session?.AttachedEntity, out CharacterInterfaceComponent? comp))
- return;
-
- if (toggle)
- comp.Window?.OpenCentered();
- else
- comp.Window?.Close();
- }
-
- private void OnPlayerDetached(EntityUid uid, CharacterInterfaceComponent comp, PlayerDetachedEvent args)
- {
- if (comp.Window == null)
- return;
-
- _gameHud.CharacterButtonVisible = false;
- _gameHud.CharacterButtonToggled -= ToggleWindow;
- comp.Window.Close();
- }
-
- private void HandleOpenCharacterMenu()
- {
- if (_playerManager.LocalPlayer?.ControlledEntity == null
- || !EntityManager.TryGetComponent(_playerManager.LocalPlayer.ControlledEntity, out CharacterInterfaceComponent? characterInterface))
- return;
-
- var menu = characterInterface.Window;
- if (menu == null)
- return;
-
- if (menu.IsOpen)
- {
- if (menu.IsAtFront())
- _setOpenValue(menu, false);
- else
- menu.MoveToFront();
- }
- else
- {
- _setOpenValue(menu, true);
- }
- }
-
- private void _setOpenValue(DefaultWindow menu, bool value)
- {
- _gameHud.CharacterButtonDown = value;
- if (value)
- menu.OpenCentered();
- else
- menu.Close();
- }
- }
-}
diff --git a/Content.Client/Chat/ChatHelper.cs b/Content.Client/Chat/ChatHelper.cs
deleted file mode 100644
index a0db4f97d5..0000000000
--- a/Content.Client/Chat/ChatHelper.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Content.Shared.Chat;
-using Robust.Shared.Maths;
-
-namespace Content.Client.Chat
-{
- public sealed class ChatHelper
- {
- public static Color ChatColor(ChatChannel channel) =>
- channel switch
- {
- ChatChannel.Server => Color.Orange,
- ChatChannel.Radio => Color.LimeGreen,
- ChatChannel.LOOC => Color.MediumTurquoise,
- ChatChannel.OOC => Color.LightSkyBlue,
- ChatChannel.Dead => Color.MediumPurple,
- ChatChannel.Admin => Color.Red,
- ChatChannel.Whisper => Color.DarkGray,
- _ => Color.LightGray
- };
- }
-}
diff --git a/Content.Client/Chat/ChatInput.cs b/Content.Client/Chat/ChatInput.cs
deleted file mode 100644
index 3633e66e69..0000000000
--- a/Content.Client/Chat/ChatInput.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using Content.Client.Chat.UI;
-using Content.Client.Gameplay;
-using Content.Client.Viewport;
-using Content.Shared.Chat;
-using Content.Shared.Input;
-using Robust.Client.Input;
-using Robust.Shared.Input.Binding;
-
-namespace Content.Client.Chat
-{
- public static class ChatInput
- {
- public static void SetupChatInputHandlers(IInputManager inputManager, ChatBox chatBox)
- {
- inputManager.SetInputCommand(ContentKeyFunctions.FocusChat,
- InputCmdHandler.FromDelegate(_ => GameplayState.FocusChat(chatBox)));
-
- inputManager.SetInputCommand(ContentKeyFunctions.FocusLocalChat,
- InputCmdHandler.FromDelegate(_ => GameplayState.FocusChannel(chatBox, ChatSelectChannel.Local)));
-
- inputManager.SetInputCommand(ContentKeyFunctions.FocusWhisperChat,
- InputCmdHandler.FromDelegate(_ => GameplayState.FocusChannel(chatBox, ChatSelectChannel.Whisper)));
-
- inputManager.SetInputCommand(ContentKeyFunctions.FocusOOC,
- InputCmdHandler.FromDelegate(_ => GameplayState.FocusChannel(chatBox, ChatSelectChannel.OOC)));
-
- inputManager.SetInputCommand(ContentKeyFunctions.FocusAdminChat,
- InputCmdHandler.FromDelegate(_ => GameplayState.FocusChannel(chatBox, ChatSelectChannel.Admin)));
-
- inputManager.SetInputCommand(ContentKeyFunctions.FocusRadio,
- InputCmdHandler.FromDelegate(_ => GameplayState.FocusChannel(chatBox, ChatSelectChannel.Radio)));
-
- inputManager.SetInputCommand(ContentKeyFunctions.FocusDeadChat,
- InputCmdHandler.FromDelegate(_ => GameplayState.FocusChannel(chatBox, ChatSelectChannel.Dead)));
-
- inputManager.SetInputCommand(ContentKeyFunctions.FocusConsoleChat,
- InputCmdHandler.FromDelegate(_ => GameplayState.FocusChannel(chatBox, ChatSelectChannel.Console)));
-
- inputManager.SetInputCommand(ContentKeyFunctions.CycleChatChannelForward,
- InputCmdHandler.FromDelegate(_ => chatBox.CycleChatChannel(true)));
-
- inputManager.SetInputCommand(ContentKeyFunctions.CycleChatChannelBackward,
- InputCmdHandler.FromDelegate(_ => chatBox.CycleChatChannel(false)));
- }
- }
-}
diff --git a/Content.Client/Chat/ChatSystem.cs b/Content.Client/Chat/ChatSystem.cs
index 48e7b41bae..d594347845 100644
--- a/Content.Client/Chat/ChatSystem.cs
+++ b/Content.Client/Chat/ChatSystem.cs
@@ -1,65 +1,5 @@
-using Content.Client.Chat.Managers;
-using Content.Client.Chat.UI;
-using Content.Client.Examine;
using Content.Shared.Chat;
-using Content.Shared.Examine;
-using Robust.Client.Player;
-using Robust.Shared.Map;
namespace Content.Client.Chat;
-public sealed class ChatSystem : SharedChatSystem
-{
- [Dependency] private readonly IChatManager _manager = default!;
- [Dependency] private readonly IPlayerManager _player = default!;
- [Dependency] private readonly ExamineSystem _examineSystem = default!;
-
- public override void FrameUpdate(float frameTime)
- {
- base.FrameUpdate(frameTime);
-
- var player = _player.LocalPlayer?.ControlledEntity;
- var predicate = static (EntityUid uid, (EntityUid compOwner, EntityUid? attachedEntity) data)
- => uid == data.compOwner || uid == data.attachedEntity;
- var bubbles = _manager.GetSpeechBubbles();
- var playerPos = player != null ? Transform(player.Value).MapPosition : MapCoordinates.Nullspace;
-
- var occluded = player != null && _examineSystem.IsOccluded(player.Value);
-
- foreach (var (ent, bubs) in bubbles)
- {
- if (Deleted(ent))
- {
- SetBubbles(bubs, false);
- continue;
- }
-
- if (ent == player)
- {
- SetBubbles(bubs, true);
- continue;
- }
-
- var otherPos = Transform(ent).MapPosition;
-
- if (occluded && !ExamineSystemShared.InRangeUnOccluded(
- playerPos,
- otherPos, 0f,
- (ent, player), predicate))
- {
- SetBubbles(bubs, false);
- continue;
- }
-
- SetBubbles(bubs, true);
- }
- }
-
- private void SetBubbles(List bubbles, bool value)
- {
- foreach (var bubble in bubbles)
- {
- bubble.Visible = value;
- }
- }
-}
+public sealed class ChatSystem : SharedChatSystem {}
diff --git a/Content.Client/Chat/Managers/ChatManager.cs b/Content.Client/Chat/Managers/ChatManager.cs
index 4f2c9988d5..9b5209d9a3 100644
--- a/Content.Client/Chat/Managers/ChatManager.cs
+++ b/Content.Client/Chat/Managers/ChatManager.cs
@@ -1,327 +1,29 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
using Content.Client.Administration.Managers;
-using Content.Client.Chat.UI;
-using Content.Client.Gameplay;
using Content.Client.Ghost;
-using Content.Client.Viewport;
using Content.Shared.Administration;
-using Content.Shared.CCVar;
using Content.Shared.Chat;
using Robust.Client.Console;
-using Robust.Client.Graphics;
-using Robust.Client.Player;
-using Robust.Client.State;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Configuration;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Log;
-using Robust.Shared.Network;
-using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.Chat.Managers
{
- internal sealed class ChatManager : IChatManager, IPostInjectInit
+ internal sealed class ChatManager : IChatManager
{
- private struct SpeechBubbleData
- {
- public string Message;
- public SpeechBubble.SpeechType Type;
- }
+ [Dependency] private readonly IClientConsoleHost _consoleHost = default!;
+ [Dependency] private readonly IClientAdminManager _adminMgr = default!;
+ [Dependency] private readonly IEntitySystemManager _systems = default!;
private ISawmill _sawmill = default!;
- ///
- /// The max amount of chars allowed to fit in a single speech bubble.
- ///
- private const int SingleBubbleCharLimit = 100;
-
- ///
- /// Base queue delay each speech bubble has.
- ///
- private const float BubbleDelayBase = 0.2f;
-
- ///
- /// Factor multiplied by speech bubble char length to add to delay.
- ///
- private const float BubbleDelayFactor = 0.8f / SingleBubbleCharLimit;
-
- ///
- /// The max amount of speech bubbles over a single entity at once.
- ///
- private const int SpeechBubbleCap = 4;
-
- ///
- /// The max amount of characters an entity can send in one message
- ///
- public int MaxMessageLength => _cfg.GetCVar(CCVars.ChatMaxMessageLength);
-
- private readonly List _history = new();
- public IReadOnlyList History => _history;
-
- // currently enabled channel filters set by the user.
- // All values default to on, even if they aren't a filterable chat channel currently.
- // Note that these are persisted here, at the manager,
- // rather than the chatbox so that these settings persist between instances of different
- // chatboxes.
- public ChatChannel ChannelFilters { get; private set; } = (ChatChannel) ushort.MaxValue;
-
- // Maintains which channels a client should be able to filter (for showing in the chatbox)
- // and select (for attempting to send on).
- // This may not always actually match with what the server will actually allow them to
- // send / receive on, it is only what the user can select in the UI. For example,
- // if a user is silenced from speaking for some reason this may still contain ChatChannel.Local, it is left up
- // to the server to handle invalid attempts to use particular channels and not send messages for
- // channels the user shouldn't be able to hear.
- //
- // Note that Command is an available selection in the chatbox channel selector,
- // which is not actually a chat channel but is always available.
- public ChatSelectChannel SelectableChannels { get; private set; }
- public ChatChannel FilterableChannels { get; private set; }
-
- ///
- /// For currently disabled chat filters,
- /// unread messages (messages received since the channel has been filtered out).
- ///
- private readonly Dictionary _unreadMessages = new();
-
- public IReadOnlyDictionary UnreadMessages => _unreadMessages;
-
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IClientNetManager _netManager = default!;
- [Dependency] private readonly IClientConsoleHost _consoleHost = default!;
- [Dependency] private readonly IEntityManager _entityManager = default!;
- [Dependency] private readonly IEyeManager _eyeManager = default!;
- [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
- [Dependency] private readonly IClientAdminManager _adminMgr = default!;
- [Dependency] private readonly IConfigurationManager _cfg = default!;
- [Dependency] private readonly IStateManager _stateManager = default!;
-
- ///
- /// Current chat box control. This can be modified, so do not depend on saving a reference to this.
- ///
- public ChatBox? CurrentChatBox { get; private set; }
-
- ///
- /// Invoked when CurrentChatBox is resized (including after setting initial default size)
- ///
- public event Action? OnChatBoxResized;
-
- public event Action? ChatPermissionsUpdated;
- public event Action? UnreadMessageCountsUpdated;
- public event Action? MessageAdded;
- public event Action? FiltersUpdated;
-
- private Control _speechBubbleRoot = null!;
-
- ///
- /// Speech bubbles that are currently visible on screen.
- /// We track them to push them up when new ones get added.
- ///
- private readonly Dictionary> _activeSpeechBubbles =
- new();
-
- ///
- /// Speech bubbles that are to-be-sent because of the "rate limit" they have.
- ///
- private readonly Dictionary _queuedSpeechBubbles
- = new();
-
public void Initialize()
{
_sawmill = Logger.GetSawmill("chat");
_sawmill.Level = LogLevel.Info;
- _netManager.RegisterNetMessage(OnChatMessage);
-
- _speechBubbleRoot = new LayoutContainer();
- LayoutContainer.SetAnchorPreset(_speechBubbleRoot, LayoutContainer.LayoutPreset.Wide);
- _userInterfaceManager.StateRoot.AddChild(_speechBubbleRoot);
- _speechBubbleRoot.SetPositionFirst();
- _stateManager.OnStateChanged += _ => UpdateChannelPermissions();
}
- public IReadOnlyDictionary> GetSpeechBubbles() => _activeSpeechBubbles;
-
- public void PostInject()
+ public void SendMessage(ReadOnlyMemory text, ChatSelectChannel channel)
{
- _adminMgr.AdminStatusUpdated += UpdateChannelPermissions;
- _playerManager.LocalPlayerChanged += OnLocalPlayerChanged;
- OnLocalPlayerChanged(new LocalPlayerChangedEventArgs(null, _playerManager.LocalPlayer));
- }
-
- private void OnLocalPlayerChanged(LocalPlayerChangedEventArgs obj)
- {
- if (obj.OldPlayer != null)
- {
- obj.OldPlayer.EntityAttached -= OnLocalPlayerEntityAttached;
- obj.OldPlayer.EntityDetached -= OnLocalPlayerEntityDetached;
- }
-
- if (obj.NewPlayer != null)
- {
- obj.NewPlayer.EntityAttached += OnLocalPlayerEntityAttached;
- obj.NewPlayer.EntityDetached += OnLocalPlayerEntityDetached;
- }
-
- UpdateChannelPermissions();
- }
-
- private void OnLocalPlayerEntityAttached(EntityAttachedEventArgs obj)
- {
- UpdateChannelPermissions();
- }
-
- private void OnLocalPlayerEntityDetached(EntityDetachedEventArgs obj)
- {
- UpdateChannelPermissions();
- }
-
- // go through all of the various channels and update filter / select permissions
- // appropriately, also enabling them if our enabledChannels dict doesn't have an entry
- // for any newly-granted channels
- private void UpdateChannelPermissions()
- {
- var oldSelectable = SelectableChannels;
- SelectableChannels = default;
- FilterableChannels = default;
-
- // Can always send console stuff.
- SelectableChannels |= ChatSelectChannel.Console;
-
- // can always send/recieve OOC
- SelectableChannels |= ChatSelectChannel.OOC;
- FilterableChannels |= ChatChannel.OOC;
- SelectableChannels |= ChatSelectChannel.LOOC;
- FilterableChannels |= ChatChannel.LOOC;
-
- // can always hear server (nobody can actually send server messages).
- FilterableChannels |= ChatChannel.Server;
-
- if (_stateManager.CurrentState is GameplayStateBase)
- {
- // can always hear local / radio / emote when in the game
- FilterableChannels |= ChatChannel.Local;
- FilterableChannels |= ChatChannel.Whisper;
- FilterableChannels |= ChatChannel.Radio;
- FilterableChannels |= ChatChannel.Emotes;
-
- // Can only send local / radio / emote when attached to a non-ghost entity.
- // TODO: this logic is iffy (checking if controlling something that's NOT a ghost), is there a better way to check this?
- if (!IsGhost)
- {
- SelectableChannels |= ChatSelectChannel.Local;
- SelectableChannels |= ChatSelectChannel.Whisper;
- SelectableChannels |= ChatSelectChannel.Radio;
- SelectableChannels |= ChatSelectChannel.Emotes;
- }
- }
-
- // Only ghosts and admins can send / see deadchat.
- if (_adminMgr.HasFlag(AdminFlags.Admin) || IsGhost)
- {
- FilterableChannels |= ChatChannel.Dead;
- SelectableChannels |= ChatSelectChannel.Dead;
- }
-
- // only admins can see / filter asay
- if (_adminMgr.HasFlag(AdminFlags.Admin))
- {
- FilterableChannels |= ChatChannel.Admin;
- SelectableChannels |= ChatSelectChannel.Admin;
- }
-
- // Necessary so that we always have a channel to fall back to.
- DebugTools.Assert((SelectableChannels & ChatSelectChannel.OOC) != 0, "OOC must always be available");
- DebugTools.Assert((FilterableChannels & ChatChannel.OOC) != 0, "OOC must always be available");
-
- // let our chatbox know all the new settings
- ChatPermissionsUpdated?.Invoke(new ChatPermissionsUpdatedEventArgs {OldSelectableChannels = oldSelectable});
- }
-
- public bool IsGhost => _playerManager.LocalPlayer?.ControlledEntity is {} uid &&
- uid.IsValid() &&
- _entityManager.HasComponent(uid);
-
- public void FrameUpdate(FrameEventArgs delta)
- {
- // Update queued speech bubbles.
- if (_queuedSpeechBubbles.Count == 0)
- {
- return;
- }
-
- foreach (var (entity, queueData) in _queuedSpeechBubbles.ShallowClone())
- {
- if (!_entityManager.EntityExists(entity))
- {
- _queuedSpeechBubbles.Remove(entity);
- continue;
- }
-
- queueData.TimeLeft -= delta.DeltaSeconds;
- if (queueData.TimeLeft > 0)
- {
- continue;
- }
-
- if (queueData.MessageQueue.Count == 0)
- {
- _queuedSpeechBubbles.Remove(entity);
- continue;
- }
-
- var msg = queueData.MessageQueue.Dequeue();
-
- queueData.TimeLeft += BubbleDelayBase + msg.Message.Length * BubbleDelayFactor;
-
- // We keep the queue around while it has 0 items. This allows us to keep the timer.
- // When the timer hits 0 and there's no messages left, THEN we can clear it up.
- CreateSpeechBubble(entity, msg);
- }
- }
-
- public void SetChatBox(ChatBox chatBox)
- {
- CurrentChatBox = chatBox;
- }
-
- public void ClearUnfilteredUnreads()
- {
- foreach (var channel in _unreadMessages.Keys.ToArray())
- {
- if ((ChannelFilters & channel) != 0)
- _unreadMessages.Remove(channel);
- }
- }
-
- public void ChatBoxOnResized(ChatResizedEventArgs chatResizedEventArgs)
- {
- OnChatBoxResized?.Invoke(chatResizedEventArgs);
- }
-
- public void RemoveSpeechBubble(EntityUid entityUid, SpeechBubble bubble)
- {
- bubble.Dispose();
-
- var list = _activeSpeechBubbles[entityUid];
- list.Remove(bubble);
-
- if (list.Count == 0)
- {
- _activeSpeechBubbles.Remove(entityUid);
- }
- }
-
- public void OnChatBoxTextSubmitted(ChatBox chatBox, ReadOnlyMemory text, ChatSelectChannel channel)
- {
- DebugTools.Assert(chatBox == CurrentChatBox);
-
var str = text.ToString();
-
switch (channel)
{
case ChatSelectChannel.Console:
@@ -346,9 +48,10 @@ namespace Content.Client.Chat.Managers
break;
case ChatSelectChannel.Dead:
- if (IsGhost)
+ if (_systems.GetEntitySystemOrNull() is {IsGhost: true})
goto case ChatSelectChannel.Local;
- else if (_adminMgr.HasFlag(AdminFlags.Admin))
+
+ if (_adminMgr.HasFlag(AdminFlags.Admin))
_consoleHost.ExecuteCommand($"dsay \"{CommandParsing.Escape(str)}\"");
else
_sawmill.Warning("Tried to speak on deadchat without being ghost or admin.");
@@ -370,190 +73,5 @@ namespace Content.Client.Chat.Managers
throw new ArgumentOutOfRangeException(nameof(channel), channel, null);
}
}
-
- public void OnFilterButtonToggled(ChatChannel channel, bool enabled)
- {
- if (enabled)
- {
- ChannelFilters |= channel;
- _unreadMessages.Remove(channel);
- UnreadMessageCountsUpdated?.Invoke();
- }
- else
- {
- ChannelFilters &= ~channel;
- }
-
- FiltersUpdated?.Invoke();
- }
-
- private void OnChatMessage(MsgChatMessage msg)
- {
- // Log all incoming chat to repopulate when filter is un-toggled
- if (!msg.HideChat)
- {
- var storedMessage = new StoredChatMessage(msg);
- _history.Add(storedMessage);
- MessageAdded?.Invoke(storedMessage);
-
- if (!storedMessage.Read)
- {
- _sawmill.Debug($"Message filtered: {storedMessage.Channel}: {storedMessage.Message}");
- if (!_unreadMessages.TryGetValue(msg.Channel, out var count))
- count = 0;
-
- count += 1;
- _unreadMessages[msg.Channel] = count;
- UnreadMessageCountsUpdated?.Invoke();
- }
- }
-
- // Local messages that have an entity attached get a speech bubble.
- if (msg.SenderEntity == default)
- return;
-
- switch (msg.Channel)
- {
- case ChatChannel.Local:
- AddSpeechBubble(msg, SpeechBubble.SpeechType.Say);
- break;
-
- case ChatChannel.Whisper:
- AddSpeechBubble(msg, SpeechBubble.SpeechType.Whisper);
- break;
-
- case ChatChannel.Dead:
- if (!IsGhost)
- break;
-
- AddSpeechBubble(msg, SpeechBubble.SpeechType.Say);
- break;
-
- case ChatChannel.Emotes:
- AddSpeechBubble(msg, SpeechBubble.SpeechType.Emote);
- break;
- }
- }
-
- private void AddSpeechBubble(MsgChatMessage msg, SpeechBubble.SpeechType speechType)
- {
- if (!_entityManager.EntityExists(msg.SenderEntity))
- {
- _sawmill.Debug("Got local chat message with invalid sender entity: {0}", msg.SenderEntity);
- return;
- }
-
- var messages = SplitMessage(FormattedMessage.RemoveMarkup(msg.Message));
-
- foreach (var message in messages)
- {
- EnqueueSpeechBubble(msg.SenderEntity, message, speechType);
- }
- }
-
- private List SplitMessage(string msg)
- {
- // Split message into words separated by spaces.
- var words = msg.Split(' ');
- var messages = new List();
- var currentBuffer = new List();
-
- // Really shoddy way to approximate word length.
- // Yes, I am aware of all the crimes here.
- // TODO: Improve this to use actual glyph width etc..
- var currentWordLength = 0;
- foreach (var word in words)
- {
- // +1 for the space.
- currentWordLength += word.Length + 1;
-
- if (currentWordLength > SingleBubbleCharLimit)
- {
- // Too long for the current speech bubble, flush it.
- messages.Add(string.Join(" ", currentBuffer));
- currentBuffer.Clear();
-
- currentWordLength = word.Length;
-
- if (currentWordLength > SingleBubbleCharLimit)
- {
- // Word is STILL too long.
- // Truncate it with an ellipse.
- messages.Add($"{word.Substring(0, SingleBubbleCharLimit - 3)}...");
- currentWordLength = 0;
- continue;
- }
- }
-
- currentBuffer.Add(word);
- }
-
- if (currentBuffer.Count != 0)
- {
- // Don't forget the last bubble.
- messages.Add(string.Join(" ", currentBuffer));
- }
-
- return messages;
- }
-
- private void EnqueueSpeechBubble(EntityUid entity, string contents, SpeechBubble.SpeechType speechType)
- {
- // Don't enqueue speech bubbles for other maps. TODO: Support multiple viewports/maps?
- if (_entityManager.GetComponent(entity).MapID != _eyeManager.CurrentMap)
- return;
-
- if (!_queuedSpeechBubbles.TryGetValue(entity, out var queueData))
- {
- queueData = new SpeechBubbleQueueData();
- _queuedSpeechBubbles.Add(entity, queueData);
- }
-
- queueData.MessageQueue.Enqueue(new SpeechBubbleData
- {
- Message = contents,
- Type = speechType,
- });
- }
-
- private void CreateSpeechBubble(EntityUid entity, SpeechBubbleData speechData)
- {
- var bubble =
- SpeechBubble.CreateSpeechBubble(speechData.Type, speechData.Message, entity, _eyeManager, this, _entityManager);
-
- if (_activeSpeechBubbles.TryGetValue(entity, out var existing))
- {
- // Push up existing bubbles above the mob's head.
- foreach (var existingBubble in existing)
- {
- existingBubble.VerticalOffset += bubble.ContentSize.Y;
- }
- }
- else
- {
- existing = new List();
- _activeSpeechBubbles.Add(entity, existing);
- }
-
- existing.Add(bubble);
- _speechBubbleRoot.AddChild(bubble);
-
- if (existing.Count > SpeechBubbleCap)
- {
- // Get the oldest to start fading fast.
- var last = existing[0];
- last.FadeNow();
- }
- }
-
- private sealed class SpeechBubbleQueueData
- {
- ///
- /// Time left until the next speech bubble can appear.
- ///
- public float TimeLeft { get; set; }
-
- public Queue MessageQueue { get; } = new();
- }
}
}
diff --git a/Content.Client/Chat/Managers/IChatManager.cs b/Content.Client/Chat/Managers/IChatManager.cs
index e59de20183..fc11745b37 100644
--- a/Content.Client/Chat/Managers/IChatManager.cs
+++ b/Content.Client/Chat/Managers/IChatManager.cs
@@ -1,55 +1,11 @@
-using System;
-using System.Collections.Generic;
-using Content.Client.Chat.UI;
using Content.Shared.Chat;
-using Robust.Shared.GameObjects;
-using Robust.Shared.Timing;
namespace Content.Client.Chat.Managers
{
public interface IChatManager
{
- ChatChannel ChannelFilters { get; }
- ChatSelectChannel SelectableChannels { get; }
- ChatChannel FilterableChannels { get; }
-
void Initialize();
- void FrameUpdate(FrameEventArgs delta);
-
- void SetChatBox(ChatBox chatBox);
-
- void RemoveSpeechBubble(EntityUid entityUid, SpeechBubble bubble);
-
- ///
- /// Current chat box control. This can be modified, so do not depend on saving a reference to this.
- ///
- ChatBox? CurrentChatBox { get; }
-
- IReadOnlyDictionary> GetSpeechBubbles();
- IReadOnlyDictionary UnreadMessages { get; }
- IReadOnlyList History { get; }
- int MaxMessageLength { get; }
- bool IsGhost { get; }
-
- ///
- /// Invoked when CurrentChatBox is resized (including after setting initial default size)
- ///
- event Action? OnChatBoxResized;
-
- event Action? ChatPermissionsUpdated;
- event Action? UnreadMessageCountsUpdated;
- event Action? MessageAdded;
- event Action? FiltersUpdated;
-
- void ClearUnfilteredUnreads();
- void ChatBoxOnResized(ChatResizedEventArgs chatResizedEventArgs);
- void OnChatBoxTextSubmitted(ChatBox chatBox, ReadOnlyMemory text, ChatSelectChannel channel);
- void OnFilterButtonToggled(ChatChannel channel, bool enabled);
- }
-
- public struct ChatPermissionsUpdatedEventArgs
- {
- public ChatSelectChannel OldSelectableChannels;
+ public void SendMessage(ReadOnlyMemory text, ChatSelectChannel channel);
}
}
diff --git a/Content.Client/Chat/UI/ChatBox.xaml b/Content.Client/Chat/UI/ChatBox.xaml
deleted file mode 100644
index 3f66665d95..0000000000
--- a/Content.Client/Chat/UI/ChatBox.xaml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Content.Client/Chat/UI/ChatBox.xaml.cs b/Content.Client/Chat/UI/ChatBox.xaml.cs
deleted file mode 100644
index f84d3c9b0f..0000000000
--- a/Content.Client/Chat/UI/ChatBox.xaml.cs
+++ /dev/null
@@ -1,754 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Content.Client.Alerts.UI;
-using Content.Client.Chat.Managers;
-using Content.Client.Chat.TypingIndicator;
-using Content.Client.Resources;
-using Content.Client.Stylesheets;
-using Content.Shared.Chat;
-using Content.Shared.Input;
-using Robust.Client.AutoGenerated;
-using Robust.Client.ResourceManagement;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Input;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
-using Robust.Shared.Log;
-using Robust.Shared.Maths;
-using Robust.Shared.Utility;
-
-namespace Content.Client.Chat.UI
-{
- [GenerateTypedNameReferences]
- [Virtual]
- public partial class ChatBox : Control
- {
- [Dependency] protected readonly IChatManager ChatMgr = default!;
-
- // order in which the available channel filters show up when available
- private static readonly ChatChannel[] ChannelFilterOrder =
- {
- ChatChannel.Local,
- ChatChannel.Whisper,
- ChatChannel.Emotes,
- ChatChannel.Radio,
- ChatChannel.OOC,
- ChatChannel.Dead,
- ChatChannel.Admin,
- ChatChannel.Server
- };
-
- // order in which the channels show up in the channel selector
- private static readonly ChatSelectChannel[] ChannelSelectorOrder =
- {
- ChatSelectChannel.Local,
- ChatSelectChannel.Whisper,
- ChatSelectChannel.Emotes,
- ChatSelectChannel.Radio,
- ChatSelectChannel.LOOC,
- ChatSelectChannel.OOC,
- ChatSelectChannel.Dead,
- ChatSelectChannel.Admin
- // NOTE: Console is not in there and it can never be permanently selected.
- // You can, however, still submit commands as console by prefixing with /.
- };
-
- public const char AliasLocal = '.';
- public const char AliasConsole = '/';
- public const char AliasDead = '\\';
- public const char AliasOOC = '[';
- public const char AliasEmotes = '@';
- public const char AliasAdmin = ']';
- public const char AliasRadio = ';';
- public const char AliasWhisper = ',';
-
- private static readonly Dictionary PrefixToChannel = new()
- {
- {AliasLocal, ChatSelectChannel.Local},
- {AliasWhisper, ChatSelectChannel.Whisper},
- {AliasConsole, ChatSelectChannel.Console},
- {AliasOOC, ChatSelectChannel.OOC},
- {AliasEmotes, ChatSelectChannel.Emotes},
- {AliasAdmin, ChatSelectChannel.Admin},
- {AliasRadio, ChatSelectChannel.Radio},
- {AliasDead, ChatSelectChannel.Dead}
- };
-
- private static readonly Dictionary ChannelPrefixes =
- PrefixToChannel.ToDictionary(kv => kv.Value, kv => kv.Key);
-
- private const float FilterPopupWidth = 110;
-
- ///
- /// The currently default channel that will be used if no prefix is specified.
- ///
- public ChatSelectChannel SelectedChannel { get; private set; } = ChatSelectChannel.OOC;
-
- ///
- /// The "preferred" channel. Will be switched to if permissions change and the channel becomes available,
- /// such as by re-entering body. Gets changed if the user manually selects a channel with the buttons.
- ///
- public ChatSelectChannel PreferredChannel { get; set; } = ChatSelectChannel.OOC;
-
- public bool ReleaseFocusOnEnter { get; set; } = true;
-
- private readonly Popup _channelSelectorPopup;
- private readonly BoxContainer _channelSelectorHBox;
- private readonly Popup _filterPopup;
- private readonly PanelContainer _filterPopupPanel;
- private readonly BoxContainer _filterVBox;
-
- ///
- /// When lobbyMode is false, will position / add to correct location in StateRoot and
- /// be resizable.
- /// wWen true, will leave layout up to parent and not be resizable.
- ///
- public ChatBox()
- {
- IoCManager.InjectDependencies(this);
- RobustXamlLoader.Load(this);
-
- LayoutContainer.SetMarginLeft(this, 4);
- LayoutContainer.SetMarginRight(this, 4);
-
- _filterPopup = new Popup
- {
- Children =
- {
- (_filterPopupPanel = new PanelContainer
- {
- StyleClasses = {StyleNano.StyleClassBorderedWindowPanel},
- Children =
- {
- new BoxContainer
- {
- Orientation = BoxContainer.LayoutOrientation.Horizontal,
- Children =
- {
- new Control {MinSize = (4, 0)},
- (_filterVBox = new BoxContainer
- {
- Margin = new Thickness(0, 10),
- Orientation = BoxContainer.LayoutOrientation.Vertical,
- SeparationOverride = 4
- })
- }
- }
- }
- })
- }
- };
-
- _channelSelectorPopup = new Popup
- {
- Children =
- {
- (_channelSelectorHBox = new BoxContainer
- {
- Orientation = BoxContainer.LayoutOrientation.Horizontal,
- SeparationOverride = 1
- })
- }
- };
-
- ChannelSelector.OnToggled += OnChannelSelectorToggled;
- FilterButton.OnToggled += OnFilterButtonToggled;
- Input.OnKeyBindDown += InputKeyBindDown;
- Input.OnTextEntered += Input_OnTextEntered;
- Input.OnTextChanged += InputOnTextChanged;
- _channelSelectorPopup.OnPopupHide += OnChannelSelectorPopupHide;
- _filterPopup.OnPopupHide += OnFilterPopupHide;
- }
-
- protected override void EnteredTree()
- {
- base.EnteredTree();
-
- ChatMgr.MessageAdded += WriteChatMessage;
- ChatMgr.ChatPermissionsUpdated += OnChatPermissionsUpdated;
- ChatMgr.UnreadMessageCountsUpdated += UpdateUnreadMessageCounts;
- ChatMgr.FiltersUpdated += Repopulate;
-
- // The chat manager may have messages logged from before there was a chat box.
- // In this case, these messages will be marked as unread despite the filters allowing them through.
- // Tell chat manager to clear these.
- ChatMgr.ClearUnfilteredUnreads();
-
- ChatPermissionsUpdated(0);
- UpdateChannelSelectButton();
- Repopulate();
- }
-
- protected override void ExitedTree()
- {
- base.ExitedTree();
-
- ChatMgr.MessageAdded -= WriteChatMessage;
- ChatMgr.ChatPermissionsUpdated -= OnChatPermissionsUpdated;
- ChatMgr.UnreadMessageCountsUpdated -= UpdateUnreadMessageCounts;
- ChatMgr.FiltersUpdated -= Repopulate;
- }
-
- private void OnChatPermissionsUpdated(ChatPermissionsUpdatedEventArgs eventArgs)
- {
- ChatPermissionsUpdated(eventArgs.OldSelectableChannels);
- }
-
- private void ChatPermissionsUpdated(ChatSelectChannel oldSelectable)
- {
- // update the channel selector
- _channelSelectorHBox.Children.Clear();
- foreach (var selectableChannel in ChannelSelectorOrder)
- {
- if ((ChatMgr.SelectableChannels & selectableChannel) == 0)
- continue;
-
- var newButton = new ChannelItemButton(selectableChannel);
- newButton.OnPressed += OnChannelSelectorItemPressed;
- _channelSelectorHBox.AddChild(newButton);
- }
-
- // Selected channel no longer available, switch to OOC?
- if ((ChatMgr.SelectableChannels & SelectedChannel) == 0)
- {
- // Handle local -> dead mapping when you e.g. ghost.
- // Only necessary for admins because they always have deadchat
- // so the normal preferred check won't see it as newly available and do nothing.
- var mappedSelect = MapLocalIfGhost(SelectedChannel);
- if ((ChatMgr.SelectableChannels & mappedSelect) != 0)
- SafelySelectChannel(mappedSelect);
- else
- SafelySelectChannel(ChatSelectChannel.OOC);
- }
-
- // If the preferred channel just became available, switch to it.
- var pref = MapLocalIfGhost(PreferredChannel);
- if ((oldSelectable & pref) == 0 && (ChatMgr.SelectableChannels & pref) != 0)
- SafelySelectChannel(pref);
-
- // update the channel filters
- _filterVBox.Children.Clear();
- foreach (var channelFilter in ChannelFilterOrder)
- {
- if ((ChatMgr.FilterableChannels & channelFilter) == 0)
- continue;
-
- int? unreadCount = null;
- if (ChatMgr.UnreadMessages.TryGetValue(channelFilter, out var unread))
- unreadCount = unread;
-
- var newCheckBox = new ChannelFilterCheckbox(channelFilter, unreadCount)
- {
- Pressed = (ChatMgr.ChannelFilters & channelFilter) != 0
- };
-
- newCheckBox.OnToggled += OnFilterCheckboxToggled;
- _filterVBox.AddChild(newCheckBox);
- }
-
- UpdateChannelSelectButton();
- }
-
- private void UpdateUnreadMessageCounts()
- {
- foreach (var channelFilter in _filterVBox.Children)
- {
- if (channelFilter is not ChannelFilterCheckbox filterCheckbox) continue;
- if (ChatMgr.UnreadMessages.TryGetValue(filterCheckbox.Channel, out var unread))
- {
- filterCheckbox.UpdateUnreadCount(unread);
- }
- else
- {
- filterCheckbox.UpdateUnreadCount(null);
- }
- }
- }
-
- private void OnFilterCheckboxToggled(BaseButton.ButtonToggledEventArgs args)
- {
- if (args.Button is not ChannelFilterCheckbox checkbox)
- return;
-
- ChatMgr.OnFilterButtonToggled(checkbox.Channel, checkbox.Pressed);
- }
-
- private void OnFilterButtonToggled(BaseButton.ButtonToggledEventArgs args)
- {
- if (args.Pressed)
- {
- var globalPos = FilterButton.GlobalPosition;
- var (minX, minY) = _filterPopupPanel.MinSize;
- var box = UIBox2.FromDimensions(globalPos - (FilterPopupWidth, 0),
- (Math.Max(minX, FilterPopupWidth), minY));
- UserInterfaceManager.ModalRoot.AddChild(_filterPopup);
- _filterPopup.Open(box);
- }
- else
- {
- _filterPopup.Close();
- }
- }
-
- private void OnChannelSelectorToggled(BaseButton.ButtonToggledEventArgs args)
- {
- if (args.Pressed)
- {
- var globalLeft = GlobalPosition.X;
- var globalBot = GlobalPosition.Y + Height;
- var box = UIBox2.FromDimensions((globalLeft, globalBot), (SizeBox.Width, AlertsUI.ChatSeparation));
- UserInterfaceManager.ModalRoot.AddChild(_channelSelectorPopup);
- _channelSelectorPopup.Open(box);
- }
- else
- {
- _channelSelectorPopup.Close();
- }
- }
-
- private void OnFilterPopupHide()
- {
- OnPopupHide(_filterPopup, FilterButton);
- }
-
- private void OnChannelSelectorPopupHide()
- {
- OnPopupHide(_channelSelectorPopup, ChannelSelector);
- }
-
- private void OnPopupHide(Control popup, BaseButton button)
- {
- UserInterfaceManager.ModalRoot.RemoveChild(popup);
-
- // this weird check here is because the hiding of the popup happens prior to the button
- // receiving the keydown, which would cause it to then become unpressed
- // and reopen immediately. To avoid this, if the popup was hidden due to clicking on the button,
- // we will not auto-unpress the button, instead leaving it up to the button toggle logic
- // (and this requires the button to be set to EnableAllKeybinds = true)
- if (UserInterfaceManager.CurrentlyHovered != button)
- {
- button.Pressed = false;
- }
- }
-
- private void OnChannelSelectorItemPressed(BaseButton.ButtonEventArgs obj)
- {
- if (obj.Button is not ChannelItemButton button)
- return;
-
- PreferredChannel = button.Channel;
- SafelySelectChannel(button.Channel);
- _channelSelectorPopup.Close();
- }
-
- public bool SafelySelectChannel(ChatSelectChannel toSelect)
- {
- toSelect = MapLocalIfGhost(toSelect);
- if ((ChatMgr.SelectableChannels & toSelect) == 0)
- return false;
-
- SelectedChannel = toSelect;
- UpdateChannelSelectButton();
- return true;
- }
-
- private void UpdateChannelSelectButton()
- {
- var (prefixChannel, _) = SplitInputContents();
-
- var channel = prefixChannel == 0 ? SelectedChannel : prefixChannel;
-
- ChannelSelector.Text = ChannelSelectorName(channel);
- ChannelSelector.Modulate = ChannelSelectColor(channel);
- }
-
- protected override void KeyBindDown(GUIBoundKeyEventArgs args)
- {
- base.KeyBindDown(args);
-
- if (args.CanFocus)
- {
- Input.GrabKeyboardFocus();
- }
- }
-
- public void CycleChatChannel(bool forward)
- {
- Input.IgnoreNext = true;
-
- var idx = Array.IndexOf(ChannelSelectorOrder, SelectedChannel);
- do
- {
- // go over every channel until we find one we can actually select.
- idx += forward ? 1 : -1;
- idx = MathHelper.Mod(idx, ChannelSelectorOrder.Length);
- } while ((ChatMgr.SelectableChannels & ChannelSelectorOrder[idx]) == 0);
-
- SafelySelectChannel(ChannelSelectorOrder[idx]);
- }
-
- private void Repopulate()
- {
- Contents.Clear();
-
- foreach (var msg in ChatMgr.History)
- {
- WriteChatMessage(msg);
- }
- }
-
- private void WriteChatMessage(StoredChatMessage message)
- {
- var messageText = FormattedMessage.EscapeText(message.Message);
- if (!string.IsNullOrEmpty(message.MessageWrap))
- {
- messageText = string.Format(message.MessageWrap, messageText);
- }
-
- Logger.DebugS("chat", $"{message.Channel}: {messageText}");
-
- if (IsFilteredOut(message.Channel))
- return;
-
- // TODO: Can make this "smarter" later by only setting it false when the message has been scrolled to
- message.Read = true;
-
- var color = message.MessageColorOverride != Color.Transparent
- ? message.MessageColorOverride
- : ChatHelper.ChatColor(message.Channel);
-
- AddLine(messageText, message.Channel, color);
- }
-
- private bool IsFilteredOut(ChatChannel channel)
- {
- return (ChatMgr.ChannelFilters & channel) == 0;
- }
-
- private void InputKeyBindDown(GUIBoundKeyEventArgs args)
- {
- if (args.Function == EngineKeyFunctions.TextReleaseFocus)
- {
- Input.ReleaseKeyboardFocus();
- args.Handle();
- return;
- }
-
- if (args.Function == ContentKeyFunctions.CycleChatChannelForward)
- {
- CycleChatChannel(true);
- args.Handle();
- return;
- }
-
- if (args.Function == ContentKeyFunctions.CycleChatChannelBackward)
- {
- CycleChatChannel(false);
- args.Handle();
- }
- }
-
- private (ChatSelectChannel selChannel, ReadOnlyMemory text) SplitInputContents()
- {
- var text = Input.Text.AsMemory().Trim();
- if (text.Length == 0)
- return default;
-
- var prefixChar = text.Span[0];
- var channel = GetChannelFromPrefix(prefixChar);
-
- if ((ChatMgr.SelectableChannels & channel) != 0)
- // Cut off prefix if it's valid and we can use the channel in question.
- text = text[1..];
- else
- channel = 0;
-
- channel = MapLocalIfGhost(channel);
-
- // Trim from start again to cut out any whitespace between the prefix and message, if any.
- return (channel, text.TrimStart());
- }
-
- private void InputOnTextChanged(LineEdit.LineEditEventArgs obj)
- {
- // Update channel select button to correct channel if we have a prefix.
- UpdateChannelSelectButton();
-
- // Warn typing indicator about change
- IoCManager.Resolve().GetEntitySystem().ClientChangedChatText();
- }
-
- private static ChatSelectChannel GetChannelFromPrefix(char prefix)
- {
- return PrefixToChannel.GetValueOrDefault(prefix);
- }
-
- public static char GetPrefixFromChannel(ChatSelectChannel channel)
- {
- return ChannelPrefixes.GetValueOrDefault(channel);
- }
-
- public static string ChannelSelectorName(ChatSelectChannel channel)
- {
- return Loc.GetString($"hud-chatbox-select-channel-{channel}");
- }
-
- public static Color ChannelSelectColor(ChatSelectChannel channel)
- {
- return channel switch
- {
- ChatSelectChannel.Radio => Color.LimeGreen,
- ChatSelectChannel.LOOC => Color.MediumTurquoise,
- ChatSelectChannel.OOC => Color.LightSkyBlue,
- ChatSelectChannel.Dead => Color.MediumPurple,
- ChatSelectChannel.Admin => Color.Red,
- _ => Color.DarkGray
- };
- }
-
- public void AddLine(string message, ChatChannel channel, Color color)
- {
- DebugTools.Assert(!Disposed);
-
- var formatted = new FormattedMessage(3);
- formatted.PushColor(color);
- formatted.AddMarkup(message);
- formatted.Pop();
- Contents.AddMessage(formatted);
- }
-
- private void Input_OnTextEntered(LineEdit.LineEditEventArgs args)
- {
- // Warn typing indicator about entered text
- IoCManager.Resolve().GetEntitySystem().ClientSubmittedChatText();
-
- if (!string.IsNullOrWhiteSpace(args.Text))
- {
- var (prefixChannel, text) = SplitInputContents();
-
- // Check if message is longer than the character limit
- if (text.Length > ChatMgr.MaxMessageLength)
- {
- string locWarning = Loc.GetString(
- "chat-manager-max-message-length",
- ("maxMessageLength", ChatMgr.MaxMessageLength));
-
- AddLine(locWarning, ChatChannel.Server, Color.Orange);
- return;
- }
-
- ChatMgr.OnChatBoxTextSubmitted(this, text, prefixChannel == 0 ? SelectedChannel : prefixChannel);
- }
-
- Input.Clear();
- UpdateChannelSelectButton();
-
- if (ReleaseFocusOnEnter)
- Input.ReleaseKeyboardFocus();
- }
-
- public void Focus(ChatSelectChannel? channel = null)
- {
- var selectStart = Index.End;
- if (channel != null)
- {
- channel = MapLocalIfGhost(channel.Value);
-
- // Channel not selectable, just do NOTHING (not even focus).
- if (!((ChatMgr.SelectableChannels & channel.Value) != 0))
- return;
-
- var (_, text) = SplitInputContents();
-
- var newPrefix = GetPrefixFromChannel(channel.Value);
- DebugTools.Assert(newPrefix != default, "Focus channel must have prefix!");
-
- if (channel == SelectedChannel)
- {
- // New selected channel is just the selected channel,
- // just remove prefix (if any) and leave text unchanged.
-
- Input.Text = text.ToString();
- selectStart = Index.Start;
- }
- else
- {
- // Change prefix to new focused channel prefix and leave text unchanged.
- Input.Text = string.Concat(newPrefix.ToString(), " ", text.Span);
- selectStart = Index.FromStart(2);
- }
-
- UpdateChannelSelectButton();
- }
-
- Input.IgnoreNext = true;
- Input.GrabKeyboardFocus();
-
- Input.CursorPosition = Input.Text.Length;
- Input.SelectionStart = selectStart.GetOffset(Input.Text.Length);
- }
-
- private ChatSelectChannel MapLocalIfGhost(ChatSelectChannel channel)
- {
- if (channel == ChatSelectChannel.Local && ChatMgr.IsGhost)
- return ChatSelectChannel.Dead;
-
- return channel;
- }
- }
-
- ///
- /// Only needed to avoid the issue where right click on the button closes the popup
- /// but leaves the button highlighted.
- ///
- public sealed class ChannelSelectorButton : Button
- {
- public ChannelSelectorButton()
- {
- // needed so the popup is untoggled regardless of which key is pressed when hovering this button.
- // If we don't have this, then right clicking the button while it's toggled on will hide
- // the popup but keep the button toggled on
- Mode = ActionMode.Press;
- EnableAllKeybinds = true;
- }
-
- protected override void KeyBindDown(GUIBoundKeyEventArgs args)
- {
- // needed since we need EnableAllKeybinds - don't double-send both UI click and Use
- if (args.Function == EngineKeyFunctions.Use)
- return;
-
- base.KeyBindDown(args);
- }
- }
-
- public sealed class FilterButton : ContainerButton
- {
- private static readonly Color ColorNormal = Color.FromHex("#7b7e9e");
- private static readonly Color ColorHovered = Color.FromHex("#9699bb");
- private static readonly Color ColorPressed = Color.FromHex("#789B8C");
-
- private readonly TextureRect _textureRect;
-
- public FilterButton()
- {
- var filterTexture = IoCManager.Resolve()
- .GetTexture("/Textures/Interface/Nano/filter.svg.96dpi.png");
-
- // needed for same reason as ChannelSelectorButton
- Mode = ActionMode.Press;
- EnableAllKeybinds = true;
-
- AddChild(
- (_textureRect = new TextureRect
- {
- Texture = filterTexture,
- HorizontalAlignment = HAlignment.Center,
- VerticalAlignment = VAlignment.Center
- })
- );
-
- ToggleMode = true;
- }
-
- protected override void KeyBindDown(GUIBoundKeyEventArgs args)
- {
- // needed since we need EnableAllKeybinds - don't double-send both UI click and Use
- if (args.Function == EngineKeyFunctions.Use) return;
- base.KeyBindDown(args);
- }
-
- private void UpdateChildColors()
- {
- if (_textureRect == null) return;
- switch (DrawMode)
- {
- case DrawModeEnum.Normal:
- _textureRect.ModulateSelfOverride = ColorNormal;
- break;
-
- case DrawModeEnum.Pressed:
- _textureRect.ModulateSelfOverride = ColorPressed;
- break;
-
- case DrawModeEnum.Hover:
- _textureRect.ModulateSelfOverride = ColorHovered;
- break;
-
- case DrawModeEnum.Disabled:
- break;
- }
- }
-
- protected override void DrawModeChanged()
- {
- base.DrawModeChanged();
- UpdateChildColors();
- }
-
- protected override void StylePropertiesChanged()
- {
- base.StylePropertiesChanged();
- UpdateChildColors();
- }
- }
-
- public sealed class ChannelItemButton : Button
- {
- public readonly ChatSelectChannel Channel;
-
- public ChannelItemButton(ChatSelectChannel channel)
- {
- Channel = channel;
- AddStyleClass(StyleNano.StyleClassChatChannelSelectorButton);
- Text = ChatBox.ChannelSelectorName(channel);
-
- var prefix = ChatBox.GetPrefixFromChannel(channel);
- if (prefix != default)
- Text = Loc.GetString("hud-chatbox-select-name-prefixed", ("name", Text), ("prefix", prefix));
- }
- }
-
- public sealed class ChannelFilterCheckbox : CheckBox
- {
- public readonly ChatChannel Channel;
-
- public ChannelFilterCheckbox(ChatChannel channel, int? unreadCount)
- {
- Channel = channel;
-
- UpdateText(unreadCount);
- }
-
- private void UpdateText(int? unread)
- {
- var name = Loc.GetString($"hud-chatbox-channel-{Channel}");
-
- if (unread > 0)
- // todo: proper fluent stuff here.
- name += " (" + (unread > 9 ? "9+" : unread) + ")";
-
- Text = name;
- }
-
- public void UpdateUnreadCount(int? unread)
- {
- UpdateText(unread);
- }
- }
-
- public readonly struct ChatResizedEventArgs
- {
- /// new bottom that the chat rect is going to have in virtual pixels
- /// after the imminent relayout
- public readonly float NewBottom;
-
- public ChatResizedEventArgs(float newBottom)
- {
- NewBottom = newBottom;
- }
- }
-}
diff --git a/Content.Client/Chat/UI/SpeechBubble.cs b/Content.Client/Chat/UI/SpeechBubble.cs
index 8f09b9f333..a74395c2a1 100644
--- a/Content.Client/Chat/UI/SpeechBubble.cs
+++ b/Content.Client/Chat/UI/SpeechBubble.cs
@@ -1,12 +1,7 @@
-using System;
using Content.Client.Chat.Managers;
-using Content.Client.Viewport;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Content.Client.Chat.UI
@@ -48,6 +43,9 @@ namespace Content.Client.Chat.UI
public Vector2 ContentSize { get; private set; }
+ // man down
+ public event Action? OnDied;
+
public static SpeechBubble CreateSpeechBubble(SpeechType type, string text, EntityUid senderEntity, IEyeManager eyeManager, IChatManager chatManager, IEntityManager entityManager)
{
switch (type)
@@ -148,7 +146,7 @@ namespace Content.Client.Chat.UI
return;
}
- _chatManager.RemoveSpeechBubble(_senderEntity, this);
+ OnDied?.Invoke(_senderEntity, this);
}
///
@@ -164,7 +162,6 @@ namespace Content.Client.Chat.UI
}
public sealed class TextSpeechBubble : SpeechBubble
-
{
public TextSpeechBubble(string text, EntityUid senderEntity, IEyeManager eyeManager, IChatManager chatManager, IEntityManager entityManager, string speechStyleClass)
: base(text, senderEntity, eyeManager, chatManager, entityManager, speechStyleClass)
diff --git a/Content.Client/CombatMode/CombatModeComponent.cs b/Content.Client/CombatMode/CombatModeComponent.cs
index c1874dae04..482a704adf 100644
--- a/Content.Client/CombatMode/CombatModeComponent.cs
+++ b/Content.Client/CombatMode/CombatModeComponent.cs
@@ -1,11 +1,7 @@
-using Content.Client.HUD;
using Content.Client.Verbs;
using Content.Shared.CombatMode;
using Content.Shared.Targeting;
-using Robust.Client.GameObjects;
using Robust.Client.Player;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
namespace Content.Client.CombatMode
{
@@ -14,7 +10,6 @@ namespace Content.Client.CombatMode
public sealed class CombatModeComponent : SharedCombatModeComponent
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IGameHud _gameHud = default!;
public override bool IsInCombatMode
{
@@ -36,14 +31,6 @@ namespace Content.Client.CombatMode
}
}
- public void PlayerDetached() { _gameHud.CombatPanelVisible = false; }
-
- public void PlayerAttached()
- {
- _gameHud.CombatPanelVisible = false; // TODO BOBBY SYSTEM Make the targeting doll actually do something.
- UpdateHud();
- }
-
private void UpdateHud()
{
if (Owner != _playerManager.LocalPlayer?.ControlledEntity)
@@ -53,7 +40,6 @@ namespace Content.Client.CombatMode
var verbs = IoCManager.Resolve().GetEntitySystem();
verbs.CloseAllMenus();
- _gameHud.TargetingZone = ActiveZone;
}
}
}
diff --git a/Content.Client/CombatMode/CombatModeSystem.cs b/Content.Client/CombatMode/CombatModeSystem.cs
index 174f3406df..6c9de790dc 100644
--- a/Content.Client/CombatMode/CombatModeSystem.cs
+++ b/Content.Client/CombatMode/CombatModeSystem.cs
@@ -1,8 +1,6 @@
-using Content.Client.HUD;
using Content.Shared.CombatMode;
using Content.Shared.Targeting;
using JetBrains.Annotations;
-using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Shared.GameStates;
using Robust.Shared.Input.Binding;
@@ -12,17 +10,12 @@ namespace Content.Client.CombatMode
[UsedImplicitly]
public sealed class CombatModeSystem : SharedCombatModeSystem
{
- [Dependency] private readonly IGameHud _gameHud = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
public override void Initialize()
{
base.Initialize();
- _gameHud.OnTargetingZoneChanged = OnTargetingZoneChanged;
-
- SubscribeLocalEvent((_, component, _) => component.PlayerAttached());
- SubscribeLocalEvent((_, component, _) => component.PlayerDetached());
SubscribeLocalEvent(OnHandleState);
}
@@ -34,7 +27,6 @@ namespace Content.Client.CombatMode
component.IsInCombatMode = state.IsInCombatMode;
component.ActiveZone = state.TargetingZone;
}
-
public override void Shutdown()
{
CommandBinds.Unregister();
diff --git a/Content.Client/Commands/OpenAHelpCommand.cs b/Content.Client/Commands/OpenAHelpCommand.cs
index 5d6ecfaed1..3d098f28a7 100644
--- a/Content.Client/Commands/OpenAHelpCommand.cs
+++ b/Content.Client/Commands/OpenAHelpCommand.cs
@@ -1,7 +1,10 @@
using System;
using Content.Client.Administration;
using Content.Client.Administration.Systems;
+using Content.Client.UserInterface.Systems.Bwoink;
+using Content.Client.UserInterface.Systems.EscapeMenu;
using Content.Shared.Administration;
+using Robust.Client.UserInterface;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
@@ -24,13 +27,14 @@ namespace Content.Client.Commands
}
if (args.Length == 0)
{
- EntitySystem.Get().Open();
+ IoCManager.Resolve().GetUIController().Open();
}
else
{
if (Guid.TryParse(args[0], out var guid))
{
- EntitySystem.Get().Open(new NetUserId(guid));
+ var targetUser = new NetUserId(guid);
+ IoCManager.Resolve().GetUIController().Open(targetUser);
}
else
{
diff --git a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs
index ba786f27ea..f403eb74cb 100644
--- a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs
+++ b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs
@@ -1,22 +1,13 @@
-using System;
-using System.Collections.Generic;
using System.Linq;
-using Content.Client.HUD;
-using Content.Client.Resources;
+using Content.Client.UserInterface.Systems.MenuBar.Widgets;
using Content.Shared.Construction.Prototypes;
-using Content.Shared.Construction.Steps;
-using Content.Shared.Tools;
-using Content.Shared.Tools.Components;
-using Robust.Client.Graphics;
using Robust.Client.Placement;
-using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.Utility;
using Robust.Shared.Enums;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
using Robust.Shared.Prototypes;
+using static Robust.Client.UserInterface.Controls.BaseButton;
namespace Content.Client.Construction.UI
{
@@ -30,8 +21,8 @@ namespace Content.Client.Construction.UI
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!;
+ [Dependency] private readonly IUserInterfaceManager _uiManager = default!;
- private readonly IGameHud _gameHud;
private readonly IConstructionMenuView _constructionView;
private ConstructionSystem? _constructionSystem;
@@ -39,10 +30,10 @@ namespace Content.Client.Construction.UI
private bool CraftingAvailable
{
- get => _gameHud.CraftingButtonVisible;
+ get => _uiManager.GetActiveUIWidget().CraftingButton.Visible;
set
{
- _gameHud.CraftingButtonVisible = value;
+ _uiManager.GetActiveUIWidget().CraftingButton.Visible = value;
if (!value)
_constructionView.Close();
}
@@ -77,12 +68,10 @@ namespace Content.Client.Construction.UI
/// Constructs a new instance of .
///
/// GUI that is being presented to.
- public ConstructionMenuPresenter(IGameHud gameHud)
+ public ConstructionMenuPresenter()
{
// This is a lot easier than a factory
IoCManager.InjectDependencies(this);
-
- _gameHud = gameHud;
_constructionView = new ConstructionMenu();
// This is required so that if we load after the system is initialized, we can bind to it immediately
@@ -94,7 +83,7 @@ namespace Content.Client.Construction.UI
_placementManager.PlacementChanged += OnPlacementChanged;
- _constructionView.OnClose += () => _gameHud.CraftingButtonDown = false;
+ _constructionView.OnClose += () => _uiManager.GetActiveUIWidget().CraftingButton.Pressed = false;
_constructionView.ClearAllGhosts += (_, _) => _constructionSystem?.ClearAllGhosts();
_constructionView.PopulateRecipes += OnViewPopulateRecipes;
_constructionView.RecipeSelected += OnViewRecipeSelected;
@@ -110,12 +99,11 @@ namespace Content.Client.Construction.UI
PopulateCategories();
OnViewPopulateRecipes(_constructionView, (string.Empty, string.Empty));
- _gameHud.CraftingButtonToggled += OnHudCraftingButtonToggled;
}
- private void OnHudCraftingButtonToggled(bool b)
+ public void OnHudCraftingButtonToggled(ButtonToggledEventArgs args)
{
- WindowOpen = b;
+ WindowOpen = args.Pressed;
}
///
@@ -128,8 +116,6 @@ namespace Content.Client.Construction.UI
_systemManager.SystemUnloaded -= OnSystemUnloaded;
_placementManager.PlacementChanged -= OnPlacementChanged;
-
- _gameHud.CraftingButtonToggled -= OnHudCraftingButtonToggled;
}
private void OnPlacementChanged(object? sender, EventArgs e)
@@ -344,7 +330,10 @@ namespace Content.Client.Construction.UI
system.ToggleCraftingWindow += SystemOnToggleMenu;
system.CraftingAvailabilityChanged += SystemCraftingAvailabilityChanged;
system.ConstructionGuideAvailable += SystemGuideAvailable;
- CraftingAvailable = system.CraftingEnabled;
+ if (_uiManager.GetActiveUIWidgetOrNull() != null)
+ {
+ CraftingAvailable = system.CraftingEnabled;
+ }
}
private void UnbindFromSystem()
@@ -362,6 +351,8 @@ namespace Content.Client.Construction.UI
private void SystemCraftingAvailabilityChanged(object? sender, CraftingAvailabilityChangedArgs e)
{
+ if (_uiManager.ActiveScreen == null)
+ return;
CraftingAvailable = e.Available;
}
@@ -375,7 +366,7 @@ namespace Content.Client.Construction.UI
if (IsAtFront)
{
WindowOpen = false;
- _gameHud.CraftingButtonDown = false; // This does not call CraftingButtonToggled
+ _uiManager.GetActiveUIWidget().CraftingButton.Pressed = false; // This does not call CraftingButtonToggled
}
else
_constructionView.MoveToFront();
@@ -383,7 +374,7 @@ namespace Content.Client.Construction.UI
else
{
WindowOpen = true;
- _gameHud.CraftingButtonDown = true; // This does not call CraftingButtonToggled
+ _uiManager.GetActiveUIWidget().CraftingButton.Pressed = true; // This does not call CraftingButtonToggled
}
}
diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj
index 07bcb7563c..cad7fd5ec4 100644
--- a/Content.Client/Content.Client.csproj
+++ b/Content.Client/Content.Client.csproj
@@ -23,6 +23,17 @@
+
+
+ GameTopMenuBar.xaml
+
+
+ HotbarGui.xaml
+
+
+ StrippingWindow.xaml
+
+
diff --git a/Content.Client/Cooldown/CooldownGraphic.cs b/Content.Client/Cooldown/CooldownGraphic.cs
index 3ef966a140..25063f8133 100644
--- a/Content.Client/Cooldown/CooldownGraphic.cs
+++ b/Content.Client/Cooldown/CooldownGraphic.cs
@@ -1,16 +1,13 @@
-using System;
-using Robust.Client.Graphics;
+using Robust.Client.Graphics;
using Robust.Client.UserInterface;
-using Robust.Shared.IoC;
-using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
namespace Content.Client.Cooldown
{
-
public sealed class CooldownGraphic : Control
{
-
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
private readonly ShaderInstance _shader;
@@ -51,6 +48,16 @@ namespace Content.Client.Cooldown
handle.UseShader(null);
}
- }
+ public void FromTime(TimeSpan start, TimeSpan end)
+ {
+ var duration = end - start;
+ var curTime = _gameTiming.CurTime;
+ var length = duration.TotalSeconds;
+ var progress = (curTime - start).TotalSeconds / length;
+ var ratio = (progress <= 1 ? (1 - progress) : (curTime - end).TotalSeconds * -5);
+ Progress = MathHelper.Clamp((float) ratio, -1, 1);
+ Visible = ratio > -1f;
+ }
+ }
}
diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs
index 78563a883c..32f02c7d44 100644
--- a/Content.Client/Entry/EntryPoint.cs
+++ b/Content.Client/Entry/EntryPoint.cs
@@ -1,12 +1,10 @@
using Content.Client.Administration.Managers;
using Content.Client.Changelog;
-using Content.Client.CharacterInterface;
using Content.Client.Chat.Managers;
using Content.Client.Options;
using Content.Client.Eui;
using Content.Client.Flash;
using Content.Client.GhostKick;
-using Content.Client.HUD;
using Content.Client.Info;
using Content.Client.Input;
using Content.Client.IoC;
@@ -31,7 +29,6 @@ using Content.Shared.Markers;
using Robust.Client;
using Robust.Client.Graphics;
using Robust.Client.Input;
-using Robust.Client.Player;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Shared.Configuration;
@@ -42,14 +39,11 @@ using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
-using Robust.Shared.Timing;
namespace Content.Client.Entry
{
public sealed class EntryPoint : GameClient
{
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IBaseClient _baseClient = default!;
[Dependency] private readonly IGameController _gameController = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
@@ -76,7 +70,6 @@ namespace Content.Client.Entry
[Dependency] private readonly GhostKickManager _ghostKick = default!;
[Dependency] private readonly ExtendedDisconnectInformationManager _extendedDisconnectInformation = default!;
[Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!;
- [Dependency] private readonly IGameHud _gameHud = default!;
public const int NetBufferSizeOverride = 2;
@@ -168,9 +161,6 @@ namespace Content.Client.Entry
_overlayManager.AddOverlay(new FlashOverlay());
_overlayManager.AddOverlay(new RadiationPulseOverlay());
- _baseClient.PlayerJoinedServer += SubscribePlayerAttachmentEvents;
- _baseClient.PlayerLeaveServer += UnsubscribePlayerAttachmentEvents;
- _gameHud.Initialize();
_chatManager.Initialize();
_clientPreferencesManager.Initialize();
_euiManager.Initialize();
@@ -194,56 +184,6 @@ namespace Content.Client.Entry
SwitchToDefaultState();
}
- public override void Update(ModUpdateLevel level, FrameEventArgs frameEventArgs)
- {
- base.Update(level, frameEventArgs);
-
- switch (level)
- {
- case ModUpdateLevel.FramePreEngine:
- // TODO: Turn IChatManager into an EntitySystem and remove the line below.
- IoCManager.Resolve().FrameUpdate(frameEventArgs);
- break;
- }
- }
-
- ///
- /// Subscribe events to the player manager after the player manager is set up
- ///
- ///
- ///
- public void SubscribePlayerAttachmentEvents(object? sender, EventArgs args)
- {
- if (_playerManager.LocalPlayer != null)
- {
- _playerManager.LocalPlayer.EntityAttached += AttachPlayerToEntity;
- _playerManager.LocalPlayer.EntityDetached += DetachPlayerFromEntity;
- }
- }
- public void UnsubscribePlayerAttachmentEvents(object? sender, EventArgs args)
- {
- if (_playerManager.LocalPlayer != null)
- {
- _playerManager.LocalPlayer.EntityAttached -= AttachPlayerToEntity;
- _playerManager.LocalPlayer.EntityDetached -= DetachPlayerFromEntity;
- }
- }
-
- public void AttachPlayerToEntity(EntityAttachedEventArgs eventArgs)
- {
- // TODO This is shitcode. Move this to an entity system, FOR FUCK'S SAKE
- _entityManager.AddComponent(eventArgs.NewEntity);
- }
-
- public void DetachPlayerFromEntity(EntityDetachedEventArgs eventArgs)
- {
- // TODO This is shitcode. Move this to an entity system, FOR FUCK'S SAKE
- if (!_entityManager.Deleted(eventArgs.OldEntity))
- {
- _entityManager.RemoveComponent(eventArgs.OldEntity);
- }
- }
-
private void SwitchToDefaultState(bool disconnected = false)
{
// Fire off into state dependent on launcher or not.
diff --git a/Content.Client/Gameplay/GameplayState.cs b/Content.Client/Gameplay/GameplayState.cs
index 68e99bed0d..dece86421d 100644
--- a/Content.Client/Gameplay/GameplayState.cs
+++ b/Content.Client/Gameplay/GameplayState.cs
@@ -1,14 +1,8 @@
-using Content.Client.Alerts.UI;
-using Content.Client.Chat;
-using Content.Client.Chat.Managers;
-using Content.Client.Chat.UI;
using Content.Client.Construction.UI;
using Content.Client.Hands;
-using Content.Client.HUD;
using Content.Client.UserInterface.Controls;
+using Content.Client.UserInterface.Screens;
using Content.Client.Viewport;
-using Content.Client.Voting;
-using Content.Shared.Chat;
using Content.Shared.CCVar;
using Robust.Client.Graphics;
using Robust.Client.Input;
@@ -22,40 +16,26 @@ namespace Content.Client.Gameplay
{
public sealed class GameplayState : GameplayStateBase, IMainViewportState
{
- public static readonly Vector2i ViewportSize = (EyeManager.PixelsPerMeter * 21, EyeManager.PixelsPerMeter * 15);
-
- [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
- [Dependency] private readonly IGameHud _gameHud = default!;
- [Dependency] private readonly IInputManager _inputManager = default!;
- [Dependency] private readonly IChatManager _chatManager = default!;
- [Dependency] private readonly IVoteManager _voteManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
- [ViewVariables] private ChatBox? _gameChat;
- private ConstructionMenuPresenter? _constructionMenu;
- private AlertsFramePresenter? _alertsFramePresenter;
-
+ protected override Type? LinkedScreenType => typeof(DefaultGameScreen);
+ public static readonly Vector2i ViewportSize = (EyeManager.PixelsPerMeter * 21, EyeManager.PixelsPerMeter * 15);
private FpsCounter _fpsCounter = default!;
public MainViewport Viewport { get; private set; } = default!;
+ public GameplayState()
+ {
+ IoCManager.InjectDependencies(this);
+ }
+
protected override void Startup()
{
base.Startup();
-
- _gameChat = new HudChatBox {PreferredChannel = ChatSelectChannel.Local};
-
- UserInterfaceManager.StateRoot.AddChild(_gameChat);
- LayoutContainer.SetAnchorAndMarginPreset(_gameChat, LayoutContainer.LayoutPreset.TopRight, margin: 10);
- LayoutContainer.SetAnchorAndMarginPreset(_gameChat, LayoutContainer.LayoutPreset.TopRight, margin: 10);
- LayoutContainer.SetMarginLeft(_gameChat, -475);
- LayoutContainer.SetMarginBottom(_gameChat, HudChatBox.InitialChatBottom);
-
- _chatManager.ChatBoxOnResized(new ChatResizedEventArgs(HudChatBox.InitialChatBottom));
-
Viewport = new MainViewport
{
Viewport =
@@ -63,25 +43,13 @@ namespace Content.Client.Gameplay
ViewportSize = ViewportSize
}
};
-
- _userInterfaceManager.StateRoot.AddChild(Viewport);
+ UserInterfaceManager.StateRoot.AddChild(Viewport);
LayoutContainer.SetAnchorPreset(Viewport, LayoutContainer.LayoutPreset.Wide);
Viewport.SetPositionFirst();
-
- _userInterfaceManager.StateRoot.AddChild(_gameHud.RootControl);
- _chatManager.SetChatBox(_gameChat);
- _voteManager.SetPopupContainer(_gameHud.VoteContainer);
-
- ChatInput.SetupChatInputHandlers(_inputManager, _gameChat);
-
- SetupPresenters();
-
_eyeManager.MainViewport = Viewport.Viewport;
-
_overlayManager.AddOverlay(new ShowHandItemOverlay());
-
_fpsCounter = new FpsCounter(_gameTiming);
- _userInterfaceManager.StateRoot.AddChild(_fpsCounter);
+ UserInterfaceManager.PopupRoot.AddChild(_fpsCounter);
_fpsCounter.Visible = _configurationManager.GetCVar(CCVars.HudFpsCounterVisible);
_configurationManager.OnValueChanged(CCVars.HudFpsCounterVisible, (show) => { _fpsCounter.Visible = show; });
}
@@ -89,56 +57,13 @@ namespace Content.Client.Gameplay
protected override void Shutdown()
{
_overlayManager.RemoveOverlay();
- DisposePresenters();
base.Shutdown();
-
- _gameChat?.Dispose();
Viewport.Dispose();
- _gameHud.RootControl.Orphan();
// Clear viewport to some fallback, whatever.
- _eyeManager.MainViewport = _userInterfaceManager.MainViewport;
+ _eyeManager.MainViewport = UserInterfaceManager.MainViewport;
_fpsCounter.Dispose();
- }
-
- ///
- /// All UI Presenters should be constructed in here.
- ///
- private void SetupPresenters()
- {
- // HUD
- _alertsFramePresenter = new AlertsFramePresenter();
-
- // Windows
- _constructionMenu = new ConstructionMenuPresenter(_gameHud);
- }
-
- ///
- /// All UI Presenters should be disposed in here.
- ///
- private void DisposePresenters()
- {
- // Windows
- _constructionMenu?.Dispose();
-
- // HUD
- _alertsFramePresenter?.Dispose();
- }
-
- internal static void FocusChat(ChatBox chat)
- {
- if (chat.UserInterfaceManager.KeyboardFocused != null)
- return;
-
- chat.Focus();
- }
-
- internal static void FocusChannel(ChatBox chat, ChatSelectChannel channel)
- {
- if (chat.UserInterfaceManager.KeyboardFocused != null)
- return;
-
- chat.Focus(channel);
+ _uiManager.ClearWindows();
}
public override void FrameUpdate(FrameEventArgs e)
diff --git a/Content.Client/Ghost/GhostComponent.cs b/Content.Client/Ghost/GhostComponent.cs
index 57f7b7753a..3b228a8f8b 100644
--- a/Content.Client/Ghost/GhostComponent.cs
+++ b/Content.Client/Ghost/GhostComponent.cs
@@ -1,6 +1,4 @@
-using Content.Client.UserInterface.Systems.Ghost;
using Content.Shared.Ghost;
-using Robust.Client.Player;
namespace Content.Client.Ghost
{
@@ -8,24 +6,6 @@ namespace Content.Client.Ghost
[ComponentReference(typeof(SharedGhostComponent))]
public sealed class GhostComponent : SharedGhostComponent
{
- [Dependency] private readonly IPlayerManager _playerManager = default!;
-
- public GhostGui? Gui { get; set; }
public bool IsAttached { get; set; }
-
- public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
- {
- base.HandleComponentState(curState, nextState);
-
- if (curState is not GhostComponentState)
- {
- return;
- }
-
- if (Owner == _playerManager.LocalPlayer?.ControlledEntity)
- {
- Gui?.Update();
- }
- }
}
}
diff --git a/Content.Client/Ghost/GhostSystem.cs b/Content.Client/Ghost/GhostSystem.cs
index f44ba7b31a..09e0331cdf 100644
--- a/Content.Client/Ghost/GhostSystem.cs
+++ b/Content.Client/Ghost/GhostSystem.cs
@@ -1,21 +1,19 @@
-using Content.Client.HUD;
-using Content.Client.UserInterface.Systems.Ghost;
using Content.Shared.Ghost;
using JetBrains.Annotations;
+using Robust.Client.Console;
using Robust.Client.GameObjects;
using Robust.Client.Player;
+using Robust.Shared.GameStates;
namespace Content.Client.Ghost
{
[UsedImplicitly]
public sealed class GhostSystem : SharedGhostSystem
{
+ [Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IGameHud _gameHud = default!;
- // Changes to this value are manually propagated.
- // No good way to get an event into the UI.
- public int AvailableGhostRoleCount { get; private set; } = 0;
+ public int AvailableGhostRoleCount { get; private set; }
private bool _ghostVisibility = true;
@@ -38,23 +36,36 @@ namespace Content.Client.Ghost
}
}
+ public GhostComponent? Player => CompOrNull(_playerManager.LocalPlayer?.ControlledEntity);
+ public bool IsGhost => Player != null;
+
+ public event Action? PlayerRemoved;
+ public event Action? PlayerUpdated;
+ public event Action? PlayerAttached;
+ public event Action? PlayerDetached;
+ public event Action? GhostWarpsResponse;
+ public event Action? GhostRoleCountUpdated;
+
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnGhostInit);
SubscribeLocalEvent(OnGhostRemove);
+ SubscribeLocalEvent(OnGhostState);
SubscribeLocalEvent(OnGhostPlayerAttach);
SubscribeLocalEvent(OnGhostPlayerDetach);
+ SubscribeLocalEvent(OnPlayerAttach);
+
SubscribeNetworkEvent(OnGhostWarpsResponse);
SubscribeNetworkEvent(OnUpdateGhostRoleCount);
}
private void OnGhostInit(EntityUid uid, GhostComponent component, ComponentInit args)
{
- if (EntityManager.TryGetComponent(component.Owner, out SpriteComponent? sprite))
+ if (TryComp(component.Owner, out SpriteComponent? sprite))
{
sprite.Visible = GhostVisibility;
}
@@ -62,55 +73,87 @@ namespace Content.Client.Ghost
private void OnGhostRemove(EntityUid uid, GhostComponent component, ComponentRemove args)
{
- component.Gui?.Dispose();
- component.Gui = null;
+ if (uid != _playerManager.LocalPlayer?.ControlledEntity)
+ return;
+
+ if (component.IsAttached)
+ {
+ GhostVisibility = false;
+ }
+
+ PlayerRemoved?.Invoke(component);
}
private void OnGhostPlayerAttach(EntityUid uid, GhostComponent component, PlayerAttachedEvent playerAttachedEvent)
{
- // I hate UI I hate UI I Hate UI
- if (component.Gui == null)
- {
- component.Gui = new GhostGui(component, this, EntityManager.EntityNetManager!);
- component.Gui.Update();
- }
+ if (uid != _playerManager.LocalPlayer?.ControlledEntity)
+ return;
- _gameHud.HandsContainer.AddChild(component.Gui);
GhostVisibility = true;
component.IsAttached = true;
+ PlayerAttached?.Invoke(component);
+ }
+
+ private void OnGhostState(EntityUid uid, GhostComponent component, ref ComponentHandleState args)
+ {
+ if (uid != _playerManager.LocalPlayer?.ControlledEntity)
+ return;
+
+ PlayerUpdated?.Invoke(component);
+ }
+
+ private bool PlayerDetach(EntityUid uid)
+ {
+ if (uid != _playerManager.LocalPlayer?.ControlledEntity)
+ return false;
+
+ GhostVisibility = false;
+ PlayerDetached?.Invoke();
+ return true;
}
private void OnGhostPlayerDetach(EntityUid uid, GhostComponent component, PlayerDetachedEvent args)
{
- component.Gui?.Parent?.RemoveChild(component.Gui);
- GhostVisibility = false;
- component.IsAttached = false;
+ if (PlayerDetach(uid))
+ component.IsAttached = false;
+ }
+
+ private void OnPlayerAttach(PlayerAttachedEvent ev)
+ {
+ if (!HasComp(ev.Entity))
+ PlayerDetach(ev.Entity);
}
private void OnGhostWarpsResponse(GhostWarpsResponseEvent msg)
{
- var entity = _playerManager.LocalPlayer?.ControlledEntity;
-
- if (entity == null ||
- !EntityManager.TryGetComponent(entity.Value, out GhostComponent? ghost))
+ if (!IsGhost)
{
return;
}
- var window = ghost.Gui?.TargetWindow;
-
- if (window != null)
- {
- window.UpdateWarps(msg.Warps);
- window.Populate();
- }
+ GhostWarpsResponse?.Invoke(msg);
}
private void OnUpdateGhostRoleCount(GhostUpdateGhostRoleCountEvent msg)
{
AvailableGhostRoleCount = msg.AvailableGhostRoles;
- foreach (var ghost in EntityManager.EntityQuery(true))
- ghost.Gui?.Update();
+ GhostRoleCountUpdated?.Invoke(msg);
+ }
+
+ public void RequestWarps()
+ {
+ RaiseNetworkEvent(new GhostWarpsRequestEvent());
+ }
+
+ public void ReturnToBody()
+ {
+ var msg = new GhostReturnToBodyRequest();
+ RaiseNetworkEvent(msg);
+ }
+
+ public void OpenGhostRoles()
+ {
+ _console.RemoteExecuteCommand(null, "ghostroles");
}
}
}
diff --git a/Content.Client/HUD/GameHud.ButtonBar.cs b/Content.Client/HUD/GameHud.ButtonBar.cs
deleted file mode 100644
index e4925213d2..0000000000
--- a/Content.Client/HUD/GameHud.ButtonBar.cs
+++ /dev/null
@@ -1,333 +0,0 @@
-using System;
-using Content.Client.HUD.UI;
-using Content.Client.Resources;
-using Content.Client.Stylesheets;
-using Content.Shared.Input;
-using Robust.Client.Input;
-using Robust.Client.ResourceManagement;
-using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Input;
-using Robust.Shared.Localization;
-using Robust.Shared.Maths;
-
-namespace Content.Client.HUD;
-
-public interface IButtonBarView
-{
- // Escape top button.
- bool EscapeButtonDown { get; set; }
- event Action EscapeButtonToggled;
-
- // Character top button.
- bool CharacterButtonDown { get; set; }
- bool CharacterButtonVisible { get; set; }
- event Action CharacterButtonToggled;
-
- // Inventory top button.
- bool InventoryButtonDown { get; set; }
- bool InventoryButtonVisible { get; set; }
- event Action InventoryButtonToggled;
-
- // Crafting top button.
- bool CraftingButtonDown { get; set; }
- bool CraftingButtonVisible { get; set; }
- event Action CraftingButtonToggled;
-
- // Actions top button.
- bool ActionsButtonDown { get; set; }
- bool ActionsButtonVisible { get; set; }
- event Action ActionsButtonToggled;
-
- // Admin top button.
- bool AdminButtonDown { get; set; }
- bool AdminButtonVisible { get; set; }
- event Action AdminButtonToggled;
-
- // Sandbox top button.
- bool SandboxButtonDown { get; set; }
- bool SandboxButtonVisible { get; set; }
- event Action SandboxButtonToggled;
-
- // Info top button
- event Action InfoButtonPressed;
- void SetInfoRed(bool value);
-}
-
-internal sealed partial class GameHud
-{
- private TopButton _buttonEscapeMenu = default!;
- private TopButton _buttonInfo = default!;
- private TopButton _buttonCharacterMenu = default!;
- private TopButton _buttonInventoryMenu = default!;
- private TopButton _buttonCraftingMenu = default!;
- private TopButton _buttonActionsMenu = default!;
- private TopButton _buttonAdminMenu = default!;
- private TopButton _buttonSandboxMenu = default!;
-
- private BoxContainer GenerateButtonBar(IResourceCache resourceCache, IInputManager inputManager)
- {
- var topButtonsContainer = new BoxContainer
- {
- Orientation = BoxContainer.LayoutOrientation.Horizontal,
- SeparationOverride = 8
- };
-
- LayoutContainer.SetAnchorAndMarginPreset(topButtonsContainer, LayoutContainer.LayoutPreset.TopLeft, margin: 10);
-
- // the icon textures here should all have the same image height (32) but different widths, so in order to ensure
- // the buttons themselves are consistent widths we set a common custom min size
- Vector2 topMinSize = (42, 64);
-
- // Escape
- {
- _buttonEscapeMenu = new TopButton(resourceCache.GetTexture("/Textures/Interface/hamburger.svg.192dpi.png"),
- EngineKeyFunctions.EscapeMenu, inputManager)
- {
- ToolTip = Loc.GetString("game-hud-open-escape-menu-button-tooltip"),
- MinSize = (70, 64),
- StyleClasses = { StyleBase.ButtonOpenRight }
- };
-
- topButtonsContainer.AddChild(_buttonEscapeMenu);
-
- _buttonEscapeMenu.OnToggled += args => EscapeButtonToggled?.Invoke(args.Pressed);
- }
-
- // Character
- {
- _buttonCharacterMenu = new TopButton(resourceCache.GetTexture("/Textures/Interface/character.svg.192dpi.png"),
- ContentKeyFunctions.OpenCharacterMenu, inputManager)
- {
- ToolTip = Loc.GetString("game-hud-open-character-menu-button-tooltip"),
- MinSize = topMinSize,
- Visible = false,
- StyleClasses = { StyleBase.ButtonSquare }
- };
-
- topButtonsContainer.AddChild(_buttonCharacterMenu);
-
- _buttonCharacterMenu.OnToggled += args => CharacterButtonToggled?.Invoke(args.Pressed);
- }
-
- // Inventory
- {
- _buttonInventoryMenu = new TopButton(resourceCache.GetTexture("/Textures/Interface/inventory.svg.192dpi.png"),
- ContentKeyFunctions.OpenInventoryMenu, inputManager)
- {
- ToolTip = Loc.GetString("game-hud-open-inventory-menu-button-tooltip"),
- MinSize = topMinSize,
- Visible = false,
- StyleClasses = { StyleBase.ButtonSquare }
- };
-
- topButtonsContainer.AddChild(_buttonInventoryMenu);
-
- _buttonInventoryMenu.OnToggled += args => InventoryButtonToggled?.Invoke(args.Pressed);
- }
-
- // Crafting
- {
- _buttonCraftingMenu = new TopButton(resourceCache.GetTexture("/Textures/Interface/hammer.svg.192dpi.png"),
- ContentKeyFunctions.OpenCraftingMenu, inputManager)
- {
- ToolTip = Loc.GetString("game-hud-open-crafting-menu-button-tooltip"),
- MinSize = topMinSize,
- Visible = false,
- StyleClasses = { StyleBase.ButtonSquare }
- };
-
- topButtonsContainer.AddChild(_buttonCraftingMenu);
-
- _buttonCraftingMenu.OnToggled += args => CraftingButtonToggled?.Invoke(args.Pressed);
- }
-
- // Actions
- {
- _buttonActionsMenu = new TopButton(resourceCache.GetTexture("/Textures/Interface/fist.svg.192dpi.png"),
- ContentKeyFunctions.OpenActionsMenu, inputManager)
- {
- ToolTip = Loc.GetString("game-hud-open-actions-menu-button-tooltip"),
- MinSize = topMinSize,
- Visible = false,
- StyleClasses = { StyleBase.ButtonSquare }
- };
-
- topButtonsContainer.AddChild(_buttonActionsMenu);
-
- _buttonActionsMenu.OnToggled += args => ActionsButtonToggled?.Invoke(args.Pressed);
- }
-
- // Admin
- {
- _buttonAdminMenu = new TopButton(resourceCache.GetTexture("/Textures/Interface/gavel.svg.192dpi.png"),
- ContentKeyFunctions.OpenAdminMenu, inputManager)
- {
- ToolTip = Loc.GetString("game-hud-open-admin-menu-button-tooltip"),
- MinSize = topMinSize,
- Visible = false,
- StyleClasses = { StyleBase.ButtonSquare }
- };
-
- topButtonsContainer.AddChild(_buttonAdminMenu);
-
- _buttonAdminMenu.OnToggled += args => AdminButtonToggled?.Invoke(args.Pressed);
- }
-
- // Sandbox
- {
- _buttonSandboxMenu = new TopButton(resourceCache.GetTexture("/Textures/Interface/sandbox.svg.192dpi.png"),
- ContentKeyFunctions.OpenSandboxWindow, inputManager)
- {
- ToolTip = Loc.GetString("game-hud-open-sandbox-menu-button-tooltip"),
- MinSize = topMinSize,
- Visible = false,
- StyleClasses = { StyleBase.ButtonSquare }
- };
-
- topButtonsContainer.AddChild(_buttonSandboxMenu);
-
- _buttonSandboxMenu.OnToggled += args => SandboxButtonToggled?.Invoke(args.Pressed);
- }
-
- // Info Window
- {
- _buttonInfo = new TopButton(resourceCache.GetTexture("/Textures/Interface/info.svg.192dpi.png"),
- ContentKeyFunctions.OpenInfo, inputManager)
- {
- ToolTip = Loc.GetString("ui-options-function-open-info"),
- MinSize = topMinSize,
- StyleClasses = { StyleBase.ButtonOpenLeft },
- ToggleMode = false
- };
-
- topButtonsContainer.AddChild(_buttonInfo);
-
- _buttonInfo.OnPressed += args => InfoButtonPressed?.Invoke();
- }
-
- return topButtonsContainer;
- }
-
- ///
- public bool EscapeButtonDown
- {
- get => _buttonEscapeMenu.Pressed;
- set => _buttonEscapeMenu.Pressed = value;
- }
-
- ///
- public event Action? EscapeButtonToggled;
-
- ///
- public bool CharacterButtonDown
- {
- get => _buttonCharacterMenu.Pressed;
- set => _buttonCharacterMenu.Pressed = value;
- }
-
- ///
- public bool CharacterButtonVisible
- {
- get => _buttonCharacterMenu.Visible;
- set => _buttonCharacterMenu.Visible = value;
- }
-
- ///
- public event Action? CharacterButtonToggled;
-
- ///
- public bool InventoryButtonDown
- {
- get => _buttonInventoryMenu.Pressed;
- set => _buttonInventoryMenu.Pressed = value;
- }
-
- ///
- public bool InventoryButtonVisible
- {
- get => _buttonInventoryMenu.Visible;
- set => _buttonInventoryMenu.Visible = value;
- }
-
- ///
- public event Action? InventoryButtonToggled;
-
- ///
- public bool CraftingButtonDown
- {
- get => _buttonCraftingMenu.Pressed;
- set => _buttonCraftingMenu.Pressed = value;
- }
-
- ///
- public bool CraftingButtonVisible
- {
- get => _buttonCraftingMenu.Visible;
- set => _buttonCraftingMenu.Visible = value;
- }
-
- ///
- public event Action? CraftingButtonToggled;
-
- ///
- public bool ActionsButtonDown
- {
- get => _buttonActionsMenu.Pressed;
- set => _buttonActionsMenu.Pressed = value;
- }
-
- ///
- public bool ActionsButtonVisible
- {
- get => _buttonActionsMenu.Visible;
- set => _buttonActionsMenu.Visible = value;
- }
-
- ///
- public event Action? ActionsButtonToggled;
-
- ///
- public bool AdminButtonDown
- {
- get => _buttonAdminMenu.Pressed;
- set => _buttonAdminMenu.Pressed = value;
- }
-
- ///
- public bool AdminButtonVisible
- {
- get => _buttonAdminMenu.Visible;
- set => _buttonAdminMenu.Visible = value;
- }
-
- ///
- public event Action? AdminButtonToggled;
-
- ///
- public bool SandboxButtonDown
- {
- get => _buttonSandboxMenu.Pressed;
- set => _buttonSandboxMenu.Pressed = value;
- }
-
- ///
- public bool SandboxButtonVisible
- {
- get => _buttonSandboxMenu.Visible;
- set => _buttonSandboxMenu.Visible = value;
- }
-
- ///
- public event Action? SandboxButtonToggled;
-
- ///
- public event Action? InfoButtonPressed;
-
- public void SetInfoRed(bool value)
- {
- if (value)
- _buttonInfo.StyleClasses.Add(TopButton.StyleClassRedTopButton);
- else
- _buttonInfo.StyleClasses.Remove(TopButton.StyleClassRedTopButton);
- }
-}
diff --git a/Content.Client/HUD/GameHud.cs b/Content.Client/HUD/GameHud.cs
deleted file mode 100644
index d31960894c..0000000000
--- a/Content.Client/HUD/GameHud.cs
+++ /dev/null
@@ -1,274 +0,0 @@
-using System;
-using System.Linq;
-using Content.Client.HUD.UI;
-using Content.Client.Info;
-using Content.Client.Administration;
-using Content.Client.Administration.Systems;
-using Content.Client.Resources;
-using Content.Client.Targeting;
-using Content.Shared.CCVar;
-using Content.Shared.HUD;
-using Content.Shared.Input;
-using Content.Shared.Targeting;
-using Robust.Client.Graphics;
-using Robust.Client.Input;
-using Robust.Client.ResourceManagement;
-using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Configuration;
-using Robust.Shared.Input.Binding;
-using Robust.Shared.IoC;
-using Robust.Shared.Log;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Utility;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
-using Control = Robust.Client.UserInterface.Control;
-using LC = Robust.Client.UserInterface.Controls.LayoutContainer;
-
-namespace Content.Client.HUD
-{
- ///
- /// Responsible for laying out the default game HUD.
- ///
- public interface IGameHud : IButtonBarView
- {
- Control RootControl { get; }
-
- Control HandsContainer { get; }
- Control SuspicionContainer { get; }
- Control BottomLeftInventoryQuickButtonContainer { get; }
- Control BottomRightInventoryQuickButtonContainer { get; }
- Control TopInventoryQuickButtonContainer { get; }
-
- bool CombatPanelVisible { get; set; }
- TargetingZone TargetingZone { get; set; }
- Action? OnTargetingZoneChanged { get; set; }
-
- Control VoteContainer { get; }
-
- void AddTopNotification(TopNotification notification);
-
- Texture GetHudTexture(string path);
-
- bool ValidateHudTheme(int idx);
-
- // Init logic.
- void Initialize();
- }
-
- internal sealed partial class GameHud : IGameHud
- {
- private TargetingDoll _targetingDoll = default!;
- private BoxContainer _combatPanelContainer = default!;
- private BoxContainer _topNotificationContainer = default!;
-
- [Dependency] private readonly IResourceCache _resourceCache = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly IInputManager _inputManager = default!;
- [Dependency] private readonly INetConfigurationManager _configManager = default!;
-
- public Control HandsContainer { get; private set; } = default!;
- public Control SuspicionContainer { get; private set; } = default!;
- public Control TopInventoryQuickButtonContainer { get; private set; } = default!;
- public Control BottomLeftInventoryQuickButtonContainer { get; private set; } = default!;
- public Control BottomRightInventoryQuickButtonContainer { get; private set; } = default!;
-
- public bool CombatPanelVisible
- {
- get => _combatPanelContainer.Visible;
- set => _combatPanelContainer.Visible = value;
- }
-
- public TargetingZone TargetingZone
- {
- get => _targetingDoll.ActiveZone;
- set => _targetingDoll.ActiveZone = value;
- }
- public Action? OnTargetingZoneChanged { get; set; }
-
- public void AddTopNotification(TopNotification notification)
- {
- _topNotificationContainer.AddChild(notification);
- }
-
- public bool ValidateHudTheme(int idx)
- {
- if (!_prototypeManager.TryIndex(idx.ToString(), out HudThemePrototype? _))
- {
- Logger.ErrorS("hud", "invalid HUD theme id {0}, using different theme",
- idx);
- var proto = _prototypeManager.EnumeratePrototypes().FirstOrDefault();
- if (proto == null)
- {
- throw new NullReferenceException("No valid HUD prototypes!");
- }
- var id = int.Parse(proto.ID);
- _configManager.SetCVar(CCVars.HudTheme, id);
- return false;
- }
- return true;
- }
-
- public Texture GetHudTexture(string path)
- {
- var id = _configManager.GetCVar("hud.theme");
- var dir = string.Empty;
- if (!_prototypeManager.TryIndex(id.ToString(), out HudThemePrototype? proto))
- {
- throw new ArgumentOutOfRangeException();
- }
- dir = proto.Path;
-
- var resourcePath = (new ResourcePath("/Textures/Interface/") / dir) / "Slots" / path;
- return _resourceCache.GetTexture(resourcePath);
- }
-
- public void Initialize()
- {
- RootControl = new LC { Name = "AAAAAAAAAAAAAAAAAAAAAA"};
- LC.SetAnchorPreset(RootControl, LC.LayoutPreset.Wide);
-
- RootControl.AddChild(GenerateButtonBar(_resourceCache, _inputManager));
-
- InventoryButtonToggled += down => TopInventoryQuickButtonContainer.Visible = down;
- InfoButtonPressed += () => ButtonInfoOnPressed();
-
- _inputManager.SetInputCommand(ContentKeyFunctions.OpenInfo,
- InputCmdHandler.FromDelegate(s => ButtonInfoOnPressed()));
-
- _combatPanelContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- HorizontalAlignment = Control.HAlignment.Left,
- VerticalAlignment = Control.VAlignment.Bottom,
- Children =
- {
- (_targetingDoll = new TargetingDoll(_resourceCache))
- }
- };
-
- LC.SetGrowHorizontal(_combatPanelContainer, LC.GrowDirection.Begin);
- LC.SetGrowVertical(_combatPanelContainer, LC.GrowDirection.Begin);
- LC.SetAnchorAndMarginPreset(_combatPanelContainer, LC.LayoutPreset.BottomRight);
- LC.SetMarginBottom(_combatPanelContainer, -10f);
-
- _targetingDoll.OnZoneChanged += args => OnTargetingZoneChanged?.Invoke(args);
-
- var centerBottomContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- SeparationOverride = 5,
- HorizontalAlignment = Control.HAlignment.Center
- };
- LC.SetAnchorAndMarginPreset(centerBottomContainer, LC.LayoutPreset.CenterBottom);
- LC.SetGrowHorizontal(centerBottomContainer, LC.GrowDirection.Both);
- LC.SetGrowVertical(centerBottomContainer, LC.GrowDirection.Begin);
- LC.SetMarginBottom(centerBottomContainer, -10f);
- RootControl.AddChild(centerBottomContainer);
-
- HandsContainer = new BoxContainer()
- {
- VerticalAlignment = Control.VAlignment.Bottom,
- HorizontalAlignment = Control.HAlignment.Center,
- Orientation = LayoutOrientation.Vertical,
- };
- BottomRightInventoryQuickButtonContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- VerticalAlignment = Control.VAlignment.Bottom,
- HorizontalAlignment = Control.HAlignment.Right
- };
- BottomLeftInventoryQuickButtonContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- VerticalAlignment = Control.VAlignment.Bottom,
- HorizontalAlignment = Control.HAlignment.Left
- };
- TopInventoryQuickButtonContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- Visible = false,
- VerticalAlignment = Control.VAlignment.Bottom,
- HorizontalAlignment = Control.HAlignment.Center
- };
- var bottomRow = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- HorizontalAlignment = Control.HAlignment.Center
-
- };
- bottomRow.AddChild(new Control {MinSize = (69, 0)}); //Padding (nice)
- bottomRow.AddChild(BottomLeftInventoryQuickButtonContainer);
- bottomRow.AddChild(HandsContainer);
- bottomRow.AddChild(BottomRightInventoryQuickButtonContainer);
- bottomRow.AddChild(new Control {MinSize = (1, 0)}); //Padding
-
-
- centerBottomContainer.AddChild(TopInventoryQuickButtonContainer);
- centerBottomContainer.AddChild(bottomRow);
-
- SuspicionContainer = new Control
- {
- HorizontalAlignment = Control.HAlignment.Center
- };
-
- var rightBottomContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- SeparationOverride = 5
- };
- LC.SetAnchorAndMarginPreset(rightBottomContainer, LC.LayoutPreset.BottomRight);
- LC.SetGrowHorizontal(rightBottomContainer, LC.GrowDirection.Begin);
- LC.SetGrowVertical(rightBottomContainer, LC.GrowDirection.Begin);
- LC.SetMarginBottom(rightBottomContainer, -10f);
- LC.SetMarginRight(rightBottomContainer, -10f);
- RootControl.AddChild(rightBottomContainer);
-
- rightBottomContainer.AddChild(_combatPanelContainer);
-
- RootControl.AddChild(SuspicionContainer);
-
- LC.SetAnchorAndMarginPreset(SuspicionContainer, LC.LayoutPreset.BottomLeft,
- margin: 10);
- LC.SetGrowHorizontal(SuspicionContainer, LC.GrowDirection.End);
- LC.SetGrowVertical(SuspicionContainer, LC.GrowDirection.Begin);
-
- _topNotificationContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- MinSize = (600, 0)
- };
- RootControl.AddChild(_topNotificationContainer);
- LC.SetAnchorPreset(_topNotificationContainer, LC.LayoutPreset.CenterTop);
- LC.SetGrowHorizontal(_topNotificationContainer, LC.GrowDirection.Both);
- LC.SetGrowVertical(_topNotificationContainer, LC.GrowDirection.End);
-
- VoteContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical
- };
- RootControl.AddChild(VoteContainer);
- LC.SetAnchorPreset(VoteContainer, LC.LayoutPreset.TopLeft);
- LC.SetMarginLeft(VoteContainer, 180);
- LC.SetMarginTop(VoteContainer, 100);
- LC.SetGrowHorizontal(VoteContainer, LC.GrowDirection.End);
- LC.SetGrowVertical(VoteContainer, LC.GrowDirection.End);
- }
-
- private void ButtonInfoOnPressed()
- {
- var bwoinkSystem = EntitySystem.Get();
- if (bwoinkSystem.IsOpen)
- {
- bwoinkSystem.Close();
- }
- else
- {
- bwoinkSystem.Open();
- }
- }
-
- public Control RootControl { get; private set; } = default!;
-
- public Control VoteContainer { get; private set; } = default!;
- }
-}
diff --git a/Content.Client/HUD/UI/TopButton.cs b/Content.Client/HUD/UI/TopButton.cs
deleted file mode 100644
index a8d2d39b3f..0000000000
--- a/Content.Client/HUD/UI/TopButton.cs
+++ /dev/null
@@ -1,230 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using Robust.Client.Graphics;
-using Robust.Client.Input;
-using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Input;
-using Robust.Shared.Localization;
-using Robust.Shared.Maths;
-using Robust.Shared.Utility;
-
-namespace Content.Client.HUD.UI;
-
-internal sealed class TopButton : ContainerButton
-{
- public const string StyleClassLabelTopButton = "topButtonLabel";
- public const string StyleClassRedTopButton = "topButtonLabel";
- private const float CustomTooltipDelay = 0.4f;
-
- private static readonly Color ColorNormal = Color.FromHex("#7b7e9e");
- private static readonly Color ColorRedNormal = Color.FromHex("#FEFEFE");
- private static readonly Color ColorHovered = Color.FromHex("#9699bb");
- private static readonly Color ColorRedHovered = Color.FromHex("#FFFFFF");
- private static readonly Color ColorPressed = Color.FromHex("#789B8C");
-
- private const float VertPad = 8f;
- private Color NormalColor => HasStyleClass(StyleClassRedTopButton) ? ColorRedNormal : ColorNormal;
- private Color HoveredColor => HasStyleClass(StyleClassRedTopButton) ? ColorRedHovered : ColorHovered;
-
- private readonly TextureRect _textureRect;
- private readonly Label _label;
- private readonly BoundKeyFunction _function;
- private readonly IInputManager _inputManager;
-
- public TopButton(Texture texture, BoundKeyFunction function, IInputManager inputManager)
- {
- _function = function;
- _inputManager = inputManager;
- TooltipDelay = CustomTooltipDelay;
-
- AddChild(
- new BoxContainer
- {
- Orientation = BoxContainer.LayoutOrientation.Vertical,
- Children =
- {
- (_textureRect = new TextureRect
- {
- TextureScale = (0.5f, 0.5f),
- Texture = texture,
- HorizontalAlignment = HAlignment.Center,
- VerticalAlignment = VAlignment.Center,
- VerticalExpand = true,
- Margin = new Thickness(0, VertPad),
- ModulateSelfOverride = NormalColor,
- Stretch = TextureRect.StretchMode.KeepCentered
- }),
- (_label = new Label
- {
- Text = ShortKeyName(_function),
- HorizontalAlignment = HAlignment.Center,
- ModulateSelfOverride = NormalColor,
- StyleClasses = {StyleClassLabelTopButton}
- })
- }
- }
- );
-
- ToggleMode = true;
- }
-
- protected override void EnteredTree()
- {
- _inputManager.OnKeyBindingAdded += OnKeyBindingChanged;
- _inputManager.OnKeyBindingRemoved += OnKeyBindingChanged;
- _inputManager.OnInputModeChanged += OnKeyBindingChanged;
- }
-
- protected override void ExitedTree()
- {
- _inputManager.OnKeyBindingAdded -= OnKeyBindingChanged;
- _inputManager.OnKeyBindingRemoved -= OnKeyBindingChanged;
- _inputManager.OnInputModeChanged -= OnKeyBindingChanged;
- }
-
-
- private void OnKeyBindingChanged(IKeyBinding obj)
- {
- _label.Text = ShortKeyName(_function);
- }
-
- private void OnKeyBindingChanged()
- {
- _label.Text = ShortKeyName(_function);
- }
-
- private string ShortKeyName(BoundKeyFunction keyFunction)
- {
- // need to use shortened key names so they fit in the buttons.
- return TryGetShortKeyName(keyFunction, out var name) ? Loc.GetString(name) : " ";
- }
-
- private bool TryGetShortKeyName(BoundKeyFunction keyFunction, [NotNullWhen(true)] out string? name)
- {
- if (_inputManager.TryGetKeyBinding(keyFunction, out var binding))
- {
- // can't possibly fit a modifier key in the top button, so omit it
- var key = binding.BaseKey;
- if (binding.Mod1 != Keyboard.Key.Unknown || binding.Mod2 != Keyboard.Key.Unknown ||
- binding.Mod3 != Keyboard.Key.Unknown)
- {
- name = null;
- return false;
- }
-
- name = null;
- name = key switch
- {
- Keyboard.Key.Apostrophe => "'",
- Keyboard.Key.Comma => ",",
- Keyboard.Key.Delete => "Del",
- Keyboard.Key.Down => "Dwn",
- Keyboard.Key.Escape => "Esc",
- Keyboard.Key.Equal => "=",
- Keyboard.Key.Home => "Hom",
- Keyboard.Key.Insert => "Ins",
- Keyboard.Key.Left => "Lft",
- Keyboard.Key.Menu => "Men",
- Keyboard.Key.Minus => "-",
- Keyboard.Key.Num0 => "0",
- Keyboard.Key.Num1 => "1",
- Keyboard.Key.Num2 => "2",
- Keyboard.Key.Num3 => "3",
- Keyboard.Key.Num4 => "4",
- Keyboard.Key.Num5 => "5",
- Keyboard.Key.Num6 => "6",
- Keyboard.Key.Num7 => "7",
- Keyboard.Key.Num8 => "8",
- Keyboard.Key.Num9 => "9",
- Keyboard.Key.Pause => "||",
- Keyboard.Key.Period => ".",
- Keyboard.Key.Return => "Ret",
- Keyboard.Key.Right => "Rgt",
- Keyboard.Key.Slash => "/",
- Keyboard.Key.Space => "Spc",
- Keyboard.Key.Tab => "Tab",
- Keyboard.Key.Tilde => "~",
- Keyboard.Key.BackSlash => "\\",
- Keyboard.Key.BackSpace => "Bks",
- Keyboard.Key.LBracket => "[",
- Keyboard.Key.MouseButton4 => "M4",
- Keyboard.Key.MouseButton5 => "M5",
- Keyboard.Key.MouseButton6 => "M6",
- Keyboard.Key.MouseButton7 => "M7",
- Keyboard.Key.MouseButton8 => "M8",
- Keyboard.Key.MouseButton9 => "M9",
- Keyboard.Key.MouseLeft => "ML",
- Keyboard.Key.MouseMiddle => "MM",
- Keyboard.Key.MouseRight => "MR",
- Keyboard.Key.NumpadDecimal => "N.",
- Keyboard.Key.NumpadDivide => "N/",
- Keyboard.Key.NumpadEnter => "Ent",
- Keyboard.Key.NumpadMultiply => "*",
- Keyboard.Key.NumpadNum0 => "0",
- Keyboard.Key.NumpadNum1 => "1",
- Keyboard.Key.NumpadNum2 => "2",
- Keyboard.Key.NumpadNum3 => "3",
- Keyboard.Key.NumpadNum4 => "4",
- Keyboard.Key.NumpadNum5 => "5",
- Keyboard.Key.NumpadNum6 => "6",
- Keyboard.Key.NumpadNum7 => "7",
- Keyboard.Key.NumpadNum8 => "8",
- Keyboard.Key.NumpadNum9 => "9",
- Keyboard.Key.NumpadSubtract => "N-",
- Keyboard.Key.PageDown => "PgD",
- Keyboard.Key.PageUp => "PgU",
- Keyboard.Key.RBracket => "]",
- Keyboard.Key.SemiColon => ";",
- _ => DefaultShortKeyName(keyFunction)
- };
- return name != null;
- }
-
- name = null;
- return false;
- }
-
- private string? DefaultShortKeyName(BoundKeyFunction keyFunction)
- {
- var name = FormattedMessage.EscapeText(_inputManager.GetKeyFunctionButtonString(keyFunction));
- return name.Length > 3 ? null : name;
- }
-
- protected override void StylePropertiesChanged()
- {
- // colors of children depend on style, so ensure we update when style is changed
- base.StylePropertiesChanged();
- UpdateChildColors();
- }
-
- private void UpdateChildColors()
- {
- if (_label == null || _textureRect == null) return;
- switch (DrawMode)
- {
- case DrawModeEnum.Normal:
- _textureRect.ModulateSelfOverride = NormalColor;
- _label.ModulateSelfOverride = NormalColor;
- break;
-
- case DrawModeEnum.Pressed:
- _textureRect.ModulateSelfOverride = ColorPressed;
- _label.ModulateSelfOverride = ColorPressed;
- break;
-
- case DrawModeEnum.Hover:
- _textureRect.ModulateSelfOverride = HoveredColor;
- _label.ModulateSelfOverride = HoveredColor;
- break;
-
- case DrawModeEnum.Disabled:
- break;
- }
- }
-
-
- protected override void DrawModeChanged()
- {
- base.DrawModeChanged();
- UpdateChildColors();
- }
-}
diff --git a/Content.Client/HUD/UI/TopNotification.cs b/Content.Client/HUD/UI/TopNotification.cs
deleted file mode 100644
index a8d3a67ae4..0000000000
--- a/Content.Client/HUD/UI/TopNotification.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Robust.Client.UserInterface;
-
-namespace Content.Client.HUD.UI
-{
- public sealed class TopNotification : Control
- {
-
- }
-}
diff --git a/Content.Client/Hands/HandButton.cs b/Content.Client/Hands/HandButton.cs
deleted file mode 100644
index a98deb4134..0000000000
--- a/Content.Client/Hands/HandButton.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using Content.Client.HUD;
-using Content.Client.Items.UI;
-using Content.Shared.Hands.Components;
-using Robust.Client.Graphics;
-using Robust.Client.UserInterface.Controls;
-
-namespace Content.Client.Hands
-{
- public sealed class HandButton : ItemSlotButton
- {
- private bool _activeHand;
- private bool _highlighted;
-
- public HandButton(int size, string textureName, string storageTextureName, IGameHud gameHud, Texture blockedTexture, HandLocation location) : base(size, textureName, storageTextureName, gameHud)
- {
- Location = location;
-
- AddChild(Blocked = new TextureRect
- {
- Texture = blockedTexture,
- TextureScale = (2, 2),
- MouseFilter = MouseFilterMode.Stop,
- Visible = false
- });
- }
-
- public HandLocation Location { get; }
- public TextureRect Blocked { get; }
-
- public void SetActiveHand(bool active)
- {
- _activeHand = active;
- UpdateHighlight();
- }
-
- public override void Highlight(bool highlight)
- {
- _highlighted = highlight;
- UpdateHighlight();
- }
-
- private void UpdateHighlight()
- {
- // always stay highlighted if active
- base.Highlight(_activeHand || _highlighted);
- }
- }
-}
diff --git a/Content.Client/Hands/HandsComponent.cs b/Content.Client/Hands/HandsComponent.cs
index 7214b098de..aa38598bfc 100644
--- a/Content.Client/Hands/HandsComponent.cs
+++ b/Content.Client/Hands/HandsComponent.cs
@@ -15,8 +15,6 @@ namespace Content.Client.Hands
[DataField("showInHands")]
public bool ShowInHands = true;
- public HandsGui? Gui { get; set; }
-
///
/// Data about the current sprite layers that the hand is contributing to the owner entity. Used for sprite in-hands.
///
diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs
index 97557d27b5..dab97ee99b 100644
--- a/Content.Client/Hands/Systems/HandsSystem.cs
+++ b/Content.Client/Hands/Systems/HandsSystem.cs
@@ -1,8 +1,7 @@
using System.Diagnostics.CodeAnalysis;
-using System.Linq;
using Content.Client.Animations;
-using Content.Client.Hands.UI;
-using Content.Client.HUD;
+using Content.Client.Examine;
+using Content.Client.Verbs;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
@@ -21,24 +20,39 @@ namespace Content.Client.Hands.Systems
public sealed class HandsSystem : SharedHandsSystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly IGameHud _gameHud = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
+
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
+ [Dependency] private readonly ExamineSystem _examine = default!;
+ [Dependency] private readonly VerbSystem _verbs = default!;
+
+ public event Action? OnPlayerAddHand;
+ public event Action? OnPlayerRemoveHand;
+ public event Action? OnPlayerSetActiveHand;
+ public event Action? OnPlayerHandsAdded;
+ public event Action? OnPlayerHandsRemoved;
+ public event Action? OnPlayerItemAdded;
+ public event Action? OnPlayerItemRemoved;
+ public event Action? OnPlayerHandBlocked;
+ public event Action? OnPlayerHandUnblocked;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(HandleContainerModified);
- SubscribeLocalEvent(HandleContainerModified);
+ SubscribeLocalEvent(HandleItemRemoved);
+ SubscribeLocalEvent(HandleItemAdded);
SubscribeLocalEvent(HandlePlayerAttached);
SubscribeLocalEvent(HandlePlayerDetached);
+ SubscribeLocalEvent(HandleCompAdd);
SubscribeLocalEvent(HandleCompRemove);
SubscribeLocalEvent(HandleComponentState);
SubscribeLocalEvent(OnVisualsChanged);
SubscribeNetworkEvent(HandlePickupAnimation);
+
+ OnHandSetActive += OnHandActivated;
}
#region StateHandling
@@ -49,30 +63,42 @@ namespace Content.Client.Hands.Systems
var handsModified = component.Hands.Count != state.Hands.Count;
var manager = EnsureComp(uid);
- foreach (var hand in state.Hands)
- {
- if (component.Hands.TryAdd(hand.Name, hand))
- {
- hand.Container = _containerSystem.EnsureContainer(uid, hand.Name, manager);
- handsModified = true;
- }
- }
if (handsModified)
{
+ List addedHands = new();
+ foreach (var hand in state.Hands)
+ {
+ if (component.Hands.TryAdd(hand.Name, hand))
+ {
+ hand.Container = _containerSystem.EnsureContainer(uid, hand.Name, manager);
+ addedHands.Add(hand);
+ }
+ }
+
foreach (var name in component.Hands.Keys)
{
if (!state.HandNames.Contains(name))
- component.Hands.Remove(name);
+ {
+ RemoveHand(uid, name, component);
+ }
+ }
+
+ foreach (var hand in addedHands)
+ {
+ AddHand(uid, hand, component);
}
component.SortedHands = new(state.HandNames);
}
- TrySetActiveHand(uid, state.ActiveHand, component);
+ if (component.ActiveHand == null && state.ActiveHand == null)
+ return; //edge case
- if (uid == _playerManager.LocalPlayer?.ControlledEntity)
- UpdateGui();
+ if (component.ActiveHand != null && state.ActiveHand != component.ActiveHand.Name)
+ {
+ SetActiveHand(uid, component.Hands[state.ActiveHand!], component);
+ }
}
#endregion
@@ -175,13 +201,64 @@ namespace Content.Client.Hands.Systems
EntityManager.RaisePredictiveEvent(new RequestActivateInHandEvent(handName));
}
- #region visuals
- private void HandleContainerModified(EntityUid uid, SharedHandsComponent handComp, ContainerModifiedMessage args)
+ public void UIInventoryExamine(string handName)
{
- if (handComp.Hands.TryGetValue(args.Container.ID, out var hand))
+ if (!TryGetPlayerHands(out var hands) ||
+ !hands.Hands.TryGetValue(handName, out var hand) ||
+ hand.HeldEntity is not { Valid: true } entity)
{
- UpdateHandVisuals(uid, args.Entity, hand);
+ return;
}
+
+ _examine.DoExamine(entity);
+ }
+
+ ///
+ /// Called when a user clicks on the little "activation" icon in the hands GUI. This is currently only used
+ /// by storage (backpacks, etc).
+ ///
+ public void UIHandOpenContextMenu(string handName)
+ {
+ if (!TryGetPlayerHands(out var hands) ||
+ !hands.Hands.TryGetValue(handName, out var hand) ||
+ hand.HeldEntity is not { Valid: true } entity)
+ {
+ return;
+ }
+
+ _verbs.VerbMenu.OpenVerbMenu(entity);
+ }
+
+ #region visuals
+
+ private void HandleItemAdded(EntityUid uid, SharedHandsComponent handComp, ContainerModifiedMessage args)
+ {
+ if (!handComp.Hands.TryGetValue(args.Container.ID, out var hand))
+ return;
+ UpdateHandVisuals(uid, args.Entity, hand);
+
+ if (uid != _playerManager.LocalPlayer?.ControlledEntity)
+ return;
+
+ OnPlayerItemAdded?.Invoke(hand.Name, args.Entity);
+
+ if (HasComp(args.Entity))
+ OnPlayerHandBlocked?.Invoke(hand.Name);
+ }
+
+ private void HandleItemRemoved(EntityUid uid, SharedHandsComponent handComp, ContainerModifiedMessage args)
+ {
+ if (!handComp.Hands.TryGetValue(args.Container.ID, out var hand))
+ return;
+ UpdateHandVisuals(uid, args.Entity, hand);
+
+ if (uid != _playerManager.LocalPlayer?.ControlledEntity)
+ return;
+
+ OnPlayerItemRemoved?.Invoke(hand.Name, args.Entity);
+
+ if (HasComp(args.Entity))
+ OnPlayerHandUnblocked?.Invoke(hand.Name);
}
///
@@ -192,9 +269,6 @@ namespace Content.Client.Hands.Systems
if (!Resolve(uid, ref handComp, ref sprite, false))
return;
- if (uid == _playerManager.LocalPlayer?.ControlledEntity)
- UpdateGui();
-
if (!handComp.ShowInHands)
return;
@@ -206,6 +280,7 @@ namespace Content.Client.Hands.Systems
{
sprite.RemoveLayer(key);
}
+
revealedLayers.Clear();
}
else
@@ -267,52 +342,76 @@ namespace Content.Client.Hands.Systems
#endregion
#region Gui
- public void UpdateGui(HandsComponent? hands = null)
- {
- if (hands == null && !TryGetPlayerHands(out hands) || hands.Gui == null)
- return;
-
- var states = hands.Hands.Values
- .Select(hand => new GuiHand(hand.Name, hand.Location, hand.HeldEntity))
- .ToArray();
-
- hands.Gui.Update(new HandsGuiState(states, hands.ActiveHand?.Name));
- }
-
- public override bool TrySetActiveHand(EntityUid uid, string? value, SharedHandsComponent? handComp = null)
- {
- if (!base.TrySetActiveHand(uid, value, handComp))
- return false;
-
- if (uid == _playerManager.LocalPlayer?.ControlledEntity)
- UpdateGui();
-
- return true;
- }
private void HandlePlayerAttached(EntityUid uid, HandsComponent component, PlayerAttachedEvent args)
{
- component.Gui = new HandsGui(component, this);
- _gameHud.HandsContainer.AddChild(component.Gui);
- component.Gui.SetPositionFirst();
- UpdateGui(component);
+ if (_playerManager.LocalPlayer?.ControlledEntity == uid)
+ OnPlayerHandsAdded?.Invoke(component);
}
- private static void HandlePlayerDetached(EntityUid uid, HandsComponent component, PlayerDetachedEvent args)
+ private void HandlePlayerDetached(EntityUid uid, HandsComponent component, PlayerDetachedEvent args)
{
- ClearGui(component);
+ OnPlayerHandsRemoved?.Invoke();
}
- private static void HandleCompRemove(EntityUid uid, HandsComponent component, ComponentRemove args)
+ private void HandleCompAdd(EntityUid uid, HandsComponent component, ComponentAdd args)
{
- ClearGui(component);
+ if (_playerManager.LocalPlayer?.ControlledEntity == uid)
+ OnPlayerHandsAdded?.Invoke(component);
}
- private static void ClearGui(HandsComponent comp)
+ private void HandleCompRemove(EntityUid uid, HandsComponent component, ComponentRemove args)
{
- comp.Gui?.Orphan();
- comp.Gui = null;
+ if (_playerManager.LocalPlayer?.ControlledEntity == uid)
+ OnPlayerHandsRemoved?.Invoke();
}
#endregion
+
+ private void AddHand(EntityUid uid, Hand newHand, SharedHandsComponent? handsComp = null)
+ {
+ AddHand(uid, newHand.Name, newHand.Location, handsComp);
+ }
+
+ public override void AddHand(EntityUid uid, string handName, HandLocation handLocation, SharedHandsComponent? handsComp = null)
+ {
+ base.AddHand(uid, handName, handLocation, handsComp);
+
+ if (uid == _playerManager.LocalPlayer?.ControlledEntity)
+ OnPlayerAddHand?.Invoke(handName, handLocation);
+
+ if (handsComp == null)
+ return;
+
+ if (handsComp.ActiveHand == null)
+ SetActiveHand(uid, handsComp.Hands[handName], handsComp);
+ }
+ public override void RemoveHand(EntityUid uid, string handName, SharedHandsComponent? handsComp = null)
+ {
+ if (uid == _playerManager.LocalPlayer?.ControlledEntity && handsComp != null &&
+ handsComp.Hands.ContainsKey(handName) && uid ==
+ _playerManager.LocalPlayer?.ControlledEntity)
+ {
+ OnPlayerRemoveHand?.Invoke(handName);
+ }
+
+ base.RemoveHand(uid, handName, handsComp);
+ }
+
+ private void OnHandActivated(SharedHandsComponent? handsComponent)
+ {
+ if (handsComponent == null)
+ return;
+
+ if (_playerManager.LocalPlayer?.ControlledEntity != handsComponent.Owner)
+ return;
+
+ if (handsComponent.ActiveHand == null)
+ {
+ OnPlayerSetActiveHand?.Invoke(null);
+ return;
+ }
+
+ OnPlayerSetActiveHand?.Invoke(handsComponent.ActiveHand.Name);
+ }
}
}
diff --git a/Content.Client/Hands/UI/HandsGui.xaml b/Content.Client/Hands/UI/HandsGui.xaml
deleted file mode 100644
index d39c044821..0000000000
--- a/Content.Client/Hands/UI/HandsGui.xaml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/Content.Client/Hands/UI/HandsGui.xaml.cs b/Content.Client/Hands/UI/HandsGui.xaml.cs
deleted file mode 100644
index 515c521024..0000000000
--- a/Content.Client/Hands/UI/HandsGui.xaml.cs
+++ /dev/null
@@ -1,258 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using Content.Client.Hands.Systems;
-using Content.Client.HUD;
-using Content.Client.Inventory;
-using Content.Client.Items.Managers;
-using Content.Client.Items.UI;
-using Content.Client.Resources;
-using Content.Shared.CCVar;
-using Content.Shared.Hands.Components;
-using Robust.Client.AutoGenerated;
-using Robust.Client.Graphics;
-using Robust.Client.ResourceManagement;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Configuration;
-using Robust.Shared.Input;
-using Robust.Shared.Timing;
-
-namespace Content.Client.Hands.UI
-{
- [GenerateTypedNameReferences]
- public sealed partial class HandsGui : Control
- {
- [Dependency] private readonly IResourceCache _resourceCache = default!;
- [Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
- [Dependency] private readonly IGameHud _gameHud = default!;
- [Dependency] private readonly INetConfigurationManager _configManager = default!;
-
- private readonly HandsSystem _handsSystem;
- private readonly HandsComponent _handsComponent;
-
- private string StorageTexture => "back.png";
- private Texture BlockedTexture => _resourceCache.GetTexture("/Textures/Interface/Default/blocked.png");
-
- private ItemStatusPanel StatusPanel { get; }
-
- [ViewVariables] private GuiHand[] _hands = Array.Empty();
-
- private string? ActiveHand { get; set; }
-
- public HandsGui(HandsComponent hands, HandsSystem handsSystem)
- {
- _handsComponent = hands;
- _handsSystem = handsSystem;
-
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
-
- StatusPanel = ItemStatusPanel.FromSide(HandLocation.Middle);
- StatusContainer.AddChild(StatusPanel);
- StatusPanel.SetPositionFirst();
- }
-
- protected override void EnteredTree()
- {
- base.EnteredTree();
-
- _configManager.OnValueChanged(CCVars.HudTheme, UpdateHudTheme);
- }
-
- protected override void ExitedTree()
- {
- base.ExitedTree();
-
- _configManager.UnsubValueChanged(CCVars.HudTheme, UpdateHudTheme);
- }
-
- public void Update(HandsGuiState state)
- {
- ActiveHand = state.ActiveHand;
- _hands = state.GuiHands;
- Array.Sort(_hands, HandOrderComparer.Instance);
- UpdateGui();
- }
-
- private void UpdateGui()
- {
- HandsContainer.DisposeAllChildren();
- var entManager = IoCManager.Resolve();
- foreach (var hand in _hands)
- {
- var newButton = MakeHandButton(hand.HandLocation);
- HandsContainer.AddChild(newButton);
- hand.HandButton = newButton;
-
- var handName = hand.Name;
- newButton.OnPressed += args => OnHandPressed(args, handName);
- newButton.OnStoragePressed += _ => OnStoragePressed(handName);
-
- _itemSlotManager.SetItemSlot(newButton, hand.HeldItem);
-
- // Show blocked overlay if hand is blocked.
- newButton.Blocked.Visible =
- hand.HeldItem != null && entManager.HasComponent(hand.HeldItem.Value);
- }
-
- if (TryGetActiveHand(out var activeHand))
- {
- activeHand.HandButton.SetActiveHand(true);
- StatusPanel.Update(activeHand.HeldItem);
- }
- }
-
- private void OnHandPressed(GUIBoundKeyEventArgs args, string handName)
- {
- if (args.Function == EngineKeyFunctions.UIClick)
- {
- _handsSystem.UIHandClick(_handsComponent, handName);
- }
- else if (TryGetHand(handName, out var hand))
- {
- _itemSlotManager.OnButtonPressed(args, hand.HeldItem);
- }
- }
-
- private void OnStoragePressed(string handName)
- {
- _handsSystem.UIHandActivate(handName);
- }
-
- private bool TryGetActiveHand([NotNullWhen(true)] out GuiHand? activeHand)
- {
- TryGetHand(ActiveHand, out activeHand);
- return activeHand != null;
- }
-
- private bool TryGetHand(string? handName, [NotNullWhen(true)] out GuiHand? foundHand)
- {
- foundHand = null;
-
- if (handName == null)
- return false;
-
- foreach (var hand in _hands)
- {
- if (hand.Name == handName)
- foundHand = hand;
- }
-
- return foundHand != null;
- }
-
- protected override void FrameUpdate(FrameEventArgs args)
- {
- base.FrameUpdate(args);
-
- foreach (var hand in _hands)
- {
- _itemSlotManager.UpdateCooldown(hand.HandButton, hand.HeldItem);
- }
- }
-
- private HandButton MakeHandButton(HandLocation buttonLocation)
- {
- var buttonTextureName = buttonLocation switch
- {
- HandLocation.Right => "hand_r.png",
- _ => "hand_l.png"
- };
-
- return new HandButton(ClientInventorySystem.ButtonSize, buttonTextureName, StorageTexture, _gameHud, BlockedTexture, buttonLocation);
- }
-
- private void UpdateHudTheme(int idx)
- {
- UpdateGui();
- }
-
- private sealed class HandOrderComparer : IComparer
- {
- public static readonly HandOrderComparer Instance = new();
-
- public int Compare(GuiHand? x, GuiHand? y)
- {
- if (ReferenceEquals(x, y)) return 0;
- if (ReferenceEquals(null, y)) return 1;
- if (ReferenceEquals(null, x)) return -1;
-
- var orderX = Map(x.HandLocation);
- var orderY = Map(y.HandLocation);
-
- return orderX.CompareTo(orderY);
-
- static int Map(HandLocation loc)
- {
- return loc switch
- {
- HandLocation.Left => 3,
- HandLocation.Middle => 2,
- HandLocation.Right => 1,
- _ => throw new ArgumentOutOfRangeException(nameof(loc), loc, null)
- };
- }
- }
- }
- }
-
- ///
- /// Info on a set of hands to be displayed.
- ///
- public sealed class HandsGuiState
- {
- ///
- /// The set of hands to be displayed.
- ///
- [ViewVariables]
- public GuiHand[] GuiHands { get; }
-
- ///
- /// The name of the currently active hand.
- ///
- [ViewVariables]
- public string? ActiveHand { get; }
-
- public HandsGuiState(GuiHand[] guiHands, string? activeHand = null)
- {
- GuiHands = guiHands;
- ActiveHand = activeHand;
- }
- }
-
- ///
- /// Info on an individual hand to be displayed.
- ///
- public sealed class GuiHand
- {
- ///
- /// The name of this hand.
- ///
- [ViewVariables]
- public string Name { get; }
-
- ///
- /// Where this hand is located.
- ///
- [ViewVariables]
- public HandLocation HandLocation { get; }
-
- ///
- /// The item being held in this hand.
- ///
- [ViewVariables]
- public EntityUid? HeldItem { get; }
-
- ///
- /// The button in the gui associated with this hand. Assumed to be set by gui shortly after being received from the client HandsComponent.
- ///
- [ViewVariables]
- public HandButton HandButton { get; set; } = default!;
-
- public GuiHand(string name, HandLocation handLocation, EntityUid? heldItem)
- {
- Name = name;
- HandLocation = handLocation;
- HeldItem = heldItem;
- }
- }
-}
diff --git a/Content.Client/Info/LinkBanner.cs b/Content.Client/Info/LinkBanner.cs
index b79293bd37..6e87dc5c40 100644
--- a/Content.Client/Info/LinkBanner.cs
+++ b/Content.Client/Info/LinkBanner.cs
@@ -1,6 +1,7 @@
using Content.Client.Changelog;
using Content.Client.Credits;
using Content.Client.Links;
+using Content.Client.UserInterface.Systems.EscapeMenu;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.IoC;
@@ -33,6 +34,7 @@ namespace Content.Client.Info
var wikiButton = new Button {Text = Loc.GetString("server-info-wiki-button")};
wikiButton.OnPressed += args => uriOpener.OpenUri(UILinks.Wiki);
var changelogButton = new ChangelogButton();
+ changelogButton.OnPressed += args => UserInterfaceManager.GetUIController().ToggleWindow();
buttons.AddChild(changelogButton);
buttons.AddChild(rulesButton);
buttons.AddChild(discordButton);
diff --git a/Content.Client/Info/RulesAndInfoWindow.cs b/Content.Client/Info/RulesAndInfoWindow.cs
index fde0394974..b93db9300e 100644
--- a/Content.Client/Info/RulesAndInfoWindow.cs
+++ b/Content.Client/Info/RulesAndInfoWindow.cs
@@ -1,4 +1,3 @@
-using Content.Client.Options.UI;
using Content.Client.UserInterface.Systems.EscapeMenu;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
@@ -20,7 +19,6 @@ namespace Content.Client.Info
{
IoCManager.InjectDependencies(this);
-
Title = Loc.GetString("ui-info-title");
var rootContainer = new TabContainer();
@@ -50,12 +48,7 @@ namespace Content.Client.Info
AddSection(tutorialList, Loc.GetString("ui-info-header-gameplay"), "Gameplay.txt", true);
AddSection(tutorialList, Loc.GetString("ui-info-header-sandbox"), "Sandbox.txt", true);
- infoControlSection.ControlsButton.OnPressed += OnOptionsPressed;
- }
-
- private void OnOptionsPressed(BaseButton.ButtonEventArgs obj)
- {
- IoCManager.Resolve().GetUIController().ToggleWindow();
+ infoControlSection.ControlsButton.OnPressed += _ => UserInterfaceManager.GetUIController().OpenWindow();
}
private static void AddSection(Info info, Control control)
diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs
index abee8f9177..420d6ea5e4 100644
--- a/Content.Client/Input/ContentContexts.cs
+++ b/Content.Client/Input/ContentContexts.cs
@@ -23,7 +23,7 @@ namespace Content.Client.Input
common.AddFunction(ContentKeyFunctions.CycleChatChannelForward);
common.AddFunction(ContentKeyFunctions.CycleChatChannelBackward);
common.AddFunction(ContentKeyFunctions.ExamineEntity);
- common.AddFunction(ContentKeyFunctions.OpenInfo);
+ common.AddFunction(ContentKeyFunctions.OpenAHelp);
common.AddFunction(ContentKeyFunctions.TakeScreenshot);
common.AddFunction(ContentKeyFunctions.TakeScreenshotNoUI);
common.AddFunction(ContentKeyFunctions.Point);
@@ -63,25 +63,16 @@ namespace Content.Client.Input
// actions should be common (for ghosts, mobs, etc)
common.AddFunction(ContentKeyFunctions.OpenActionsMenu);
- common.AddFunction(ContentKeyFunctions.Hotbar0);
- common.AddFunction(ContentKeyFunctions.Hotbar1);
- common.AddFunction(ContentKeyFunctions.Hotbar2);
- common.AddFunction(ContentKeyFunctions.Hotbar3);
- common.AddFunction(ContentKeyFunctions.Hotbar4);
- common.AddFunction(ContentKeyFunctions.Hotbar5);
- common.AddFunction(ContentKeyFunctions.Hotbar6);
- common.AddFunction(ContentKeyFunctions.Hotbar7);
- common.AddFunction(ContentKeyFunctions.Hotbar8);
- common.AddFunction(ContentKeyFunctions.Hotbar9);
- common.AddFunction(ContentKeyFunctions.Loadout1);
- common.AddFunction(ContentKeyFunctions.Loadout2);
- common.AddFunction(ContentKeyFunctions.Loadout3);
- common.AddFunction(ContentKeyFunctions.Loadout4);
- common.AddFunction(ContentKeyFunctions.Loadout5);
- common.AddFunction(ContentKeyFunctions.Loadout6);
- common.AddFunction(ContentKeyFunctions.Loadout7);
- common.AddFunction(ContentKeyFunctions.Loadout8);
- common.AddFunction(ContentKeyFunctions.Loadout9);
+
+ 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/Inventory/ClientInventoryComponent.cs b/Content.Client/Inventory/ClientInventoryComponent.cs
index ef10eaf468..d0e514d056 100644
--- a/Content.Client/Inventory/ClientInventoryComponent.cs
+++ b/Content.Client/Inventory/ClientInventoryComponent.cs
@@ -1,5 +1,5 @@
using System.Collections.Generic;
-using Content.Client.Items.UI;
+using Content.Client.UserInterface.Controls;
using Content.Shared.Inventory;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.CustomControls;
@@ -19,24 +19,15 @@ namespace Content.Client.Inventory
[Access(typeof(ClientInventorySystem))]
public sealed class ClientInventoryComponent : InventoryComponent
{
- public Control BottomLeftButtons = default!;
- public Control BottomRightButtons = default!;
- public Control TopQuickButtons = default!;
-
- public DefaultWindow InventoryWindow = default!;
-
- public readonly Dictionary> SlotButtons = new();
-
[ViewVariables]
[DataField("speciesId")] public string? SpeciesId { get; set; }
-
+ [ViewVariables]
+ public readonly Dictionary SlotData = new ();
///
/// Data about the current layers that have been added to the players sprite due to the items in each equipment slot.
///
[ViewVariables]
[Access(typeof(ClientInventorySystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
public readonly Dictionary> VisualLayerKeys = new();
-
- public bool AttachedToGameHud;
}
}
diff --git a/Content.Client/Inventory/ClientInventorySystem.cs b/Content.Client/Inventory/ClientInventorySystem.cs
index 1207556b54..5e28dba983 100644
--- a/Content.Client/Inventory/ClientInventorySystem.cs
+++ b/Content.Client/Inventory/ClientInventorySystem.cs
@@ -1,76 +1,78 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using Content.Client.Clothing;
-using Content.Client.HUD;
-using Content.Shared.Input;
-using Content.Client.Items.Managers;
-using Content.Client.Items.UI;
-using Content.Shared.CCVar;
+using Content.Client.Examine;
+using Content.Client.Storage;
+using Content.Client.UserInterface.Controls;
+using Content.Client.Verbs;
using Content.Shared.Hands.Components;
+using Content.Shared.Interaction;
+using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
-using Content.Shared.Item;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
-using Robust.Shared.Configuration;
+using Robust.Client.Player;
using Robust.Shared.Containers;
-using Robust.Shared.GameObjects;
-using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
-using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
-using Content.Shared.Interaction.Events;
namespace Content.Client.Inventory
{
[UsedImplicitly]
public sealed class ClientInventorySystem : InventorySystem
{
- [Dependency] private readonly IGameHud _gameHud = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly IConfigurationManager _config = default!;
- [Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+
[Dependency] private readonly ClothingVisualsSystem _clothingVisualsSystem = default!;
+ [Dependency] private readonly ExamineSystem _examine = default!;
+ [Dependency] private readonly VerbSystem _verbs = default!;
- public const int ButtonSize = 64;
- private const int ButtonSeparation = 4;
- private const int RightSeparation = 2;
+ public Action? EntitySlotUpdate = null;
+ public Action? OnSlotAdded = null;
+ public Action? OnSlotRemoved = null;
+ public Action? OnLinkInventory = null;
+ public Action? OnUnlinkInventory = null;
+ public Action? OnSpriteUpdate = null;
- ///
- /// Stores delegates used to create controls for a given .
- ///
- private readonly
- Dictionary>, (DefaultWindow window, Control bottomLeft, Control bottomRight, Control
- topQuick)>>
- _uiGenerateDelegates = new();
+ private readonly Queue<(ClientInventoryComponent comp, EntityEventArgs args)> _equipEventsQueue = new();
public override void Initialize()
{
base.Initialize();
- CommandBinds.Builder
- .Bind(ContentKeyFunctions.OpenInventoryMenu,
- InputCmdHandler.FromDelegate(_ => HandleOpenInventoryMenu()))
- .Register();
-
SubscribeLocalEvent(OnPlayerAttached);
- SubscribeLocalEvent(OnPlayerDetached);
SubscribeLocalEvent(OnInit);
SubscribeLocalEvent(OnShutdown);
- SubscribeLocalEvent(OnDidEquip);
- SubscribeLocalEvent(OnDidUnequip);
+ SubscribeLocalEvent((_, comp, args) =>
+ _equipEventsQueue.Enqueue((comp, args)));
+ SubscribeLocalEvent((_, comp, args) =>
+ _equipEventsQueue.Enqueue((comp, args)));
SubscribeLocalEvent(OnUseInHand);
+ }
- _config.OnValueChanged(CCVars.HudTheme, UpdateHudTheme);
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ while (_equipEventsQueue.TryDequeue(out var tuple))
+ {
+ var (component, args) = tuple;
+
+ switch (args)
+ {
+ case DidEquipEvent equipped:
+ OnDidEquip(component, equipped);
+ break;
+ case DidUnequipEvent unequipped:
+ OnDidUnequip(component, unequipped);
+ break;
+ default:
+ throw new InvalidOperationException($"Received queued event of unknown type: {args.GetType()}");
+ }
+ }
}
private void OnUseInHand(EntityUid uid, ClothingComponent component, UseInHandEvent args)
@@ -81,85 +83,60 @@ namespace Content.Client.Inventory
QuickEquip(uid, component, args);
}
- private void OnDidUnequip(EntityUid uid, ClientInventoryComponent component, DidUnequipEvent args)
+ private void OnDidUnequip(ClientInventoryComponent component, DidUnequipEvent args)
{
- UpdateComponentUISlot(uid, args.Slot, null, component);
- }
-
- private void OnDidEquip(EntityUid uid, ClientInventoryComponent component, DidEquipEvent args)
- {
- UpdateComponentUISlot(uid, args.Slot, args.Equipment, component);
- }
-
- private void UpdateComponentUISlot(EntityUid uid, string slot, EntityUid? item, ClientInventoryComponent? component = null)
- {
- if (!Resolve(uid, ref component))
+ UpdateSlot(args.Equipee, component, args.Slot);
+ if (args.Equipee != _playerManager.LocalPlayer?.ControlledEntity)
return;
+ var update = new SlotSpriteUpdate(args.SlotGroup, args.Slot, null, false);
+ OnSpriteUpdate?.Invoke(update);
+ }
- if (!component.SlotButtons.TryGetValue(slot, out var buttons))
+ private void OnDidEquip(ClientInventoryComponent component, DidEquipEvent args)
+ {
+ UpdateSlot(args.Equipee, component, args.Slot);
+ if (args.Equipee != _playerManager.LocalPlayer?.ControlledEntity)
return;
-
- UpdateUISlot(buttons, item);
- }
-
- private void UpdateUISlot(List buttons, EntityUid? entity)
- {
- foreach (var button in buttons)
- {
- _itemSlotManager.SetItemSlot(button, entity);
- }
- }
-
- private void OnPlayerDetached(EntityUid uid, ClientInventoryComponent component, PlayerDetachedEvent? args = null)
- {
- if(!component.AttachedToGameHud) return;
-
- _gameHud.InventoryButtonVisible = false;
- _gameHud.BottomLeftInventoryQuickButtonContainer.RemoveChild(component.BottomLeftButtons);
- _gameHud.BottomRightInventoryQuickButtonContainer.RemoveChild(component.BottomRightButtons);
- _gameHud.TopInventoryQuickButtonContainer.RemoveChild(component.TopQuickButtons);
- component.AttachedToGameHud = false;
+ var sprite = EntityManager.GetComponentOrNull(args.Equipment);
+ var update = new SlotSpriteUpdate(args.SlotGroup, args.Slot, sprite,
+ HasComp(args.Equipment));
+ OnSpriteUpdate?.Invoke(update);
}
private void OnShutdown(EntityUid uid, ClientInventoryComponent component, ComponentShutdown args)
{
- OnPlayerDetached(uid, component);
+ if (component.Owner != _playerManager.LocalPlayer?.ControlledEntity)
+ return;
+
+ OnUnlinkInventory?.Invoke();
}
private void OnPlayerAttached(EntityUid uid, ClientInventoryComponent component, PlayerAttachedEvent args)
{
- if(component.AttachedToGameHud) return;
-
- _gameHud.InventoryButtonVisible = true;
- _gameHud.BottomLeftInventoryQuickButtonContainer.AddChild(component.BottomLeftButtons);
- _gameHud.BottomRightInventoryQuickButtonContainer.AddChild(component.BottomRightButtons);
- _gameHud.TopInventoryQuickButtonContainer.AddChild(component.TopQuickButtons);
- component.AttachedToGameHud = true;
- }
-
- private void UpdateHudTheme(int obj)
- {
- if (!_gameHud.ValidateHudTheme(obj))
+ if (TryGetSlots(uid, out var definitions))
{
- return;
- }
-
- foreach (var inventoryComponent in EntityManager.EntityQuery(true))
- {
- foreach (var slotButton in inventoryComponent.SlotButtons)
+ foreach (var definition in definitions)
{
- foreach (var btn in slotButton.Value)
+ if (!TryGetSlotContainer(uid, definition.Name, out var container, out _, component))
+ continue;
+
+ if (!component.SlotData.TryGetValue(definition.Name, out var data))
{
- btn.RefreshTextures(_gameHud);
+ data = new SlotData(definition);
+ component.SlotData[definition.Name] = data;
}
+
+ data.Container = container;
}
}
+
+ if (uid == _playerManager.LocalPlayer?.ControlledEntity)
+ OnLinkInventory?.Invoke(component);
}
public override void Shutdown()
{
CommandBinds.Unregister();
- _config.UnsubValueChanged(CCVars.HudTheme, UpdateHudTheme);
base.Shutdown();
}
@@ -167,28 +144,77 @@ namespace Content.Client.Inventory
{
_clothingVisualsSystem.InitClothing(uid, component);
- if (!TryGetUIElements(uid, out var window, out var bottomLeft, out var bottomRight, out var topQuick,
- component))
+ if (!_prototypeManager.TryIndex(component.TemplateId, out InventoryTemplatePrototype? invTemplate))
return;
- if (TryComp(uid, out var containerManager))
+ foreach (var slot in invTemplate.Slots)
{
- foreach (var (slot, buttons) in component.SlotButtons)
- {
- if (!TryGetSlotEntity(uid, slot, out var entity, component, containerManager))
- continue;
-
- UpdateUISlot(buttons, entity);
- }
+ TryAddSlotDef(uid, component, slot);
}
-
- component.InventoryWindow = window;
- component.BottomLeftButtons = bottomLeft;
- component.BottomRightButtons = bottomRight;
- component.TopQuickButtons = topQuick;
}
- private void HoverInSlotButton(EntityUid uid, string slot, ItemSlotButton button, InventoryComponent? inventoryComponent = null, SharedHandsComponent? hands = null)
+ public void SetSlotHighlight(EntityUid owner, ClientInventoryComponent component, string slotName, bool state)
+ {
+ var oldData = component.SlotData[slotName];
+ var newData = component.SlotData[slotName] = new SlotData(oldData, state);
+ if (owner == _playerManager.LocalPlayer?.ControlledEntity)
+ EntitySlotUpdate?.Invoke(newData);
+ }
+
+ public void UpdateSlot(EntityUid owner, ClientInventoryComponent component, string slotName,
+ bool? blocked = null, bool? highlight = null)
+ {
+ var oldData = component.SlotData[slotName];
+ var newHighlight = oldData.Highlighted;
+ var newBlocked = oldData.Blocked;
+
+ if (blocked != null)
+ newBlocked = blocked.Value;
+
+ if (highlight != null)
+ newHighlight = highlight.Value;
+
+ var newData = component.SlotData[slotName] =
+ new SlotData(component.SlotData[slotName], newHighlight, newBlocked);
+ if (owner == _playerManager.LocalPlayer?.ControlledEntity)
+ EntitySlotUpdate?.Invoke(newData);
+ }
+
+ public bool TryAddSlotDef(EntityUid owner, ClientInventoryComponent component, SlotDefinition newSlotDef)
+ {
+ SlotData newSlotData = newSlotDef; //convert to slotData
+ if (!component.SlotData.TryAdd(newSlotDef.Name, newSlotData))
+ return false;
+
+
+ if (owner == _playerManager.LocalPlayer?.ControlledEntity)
+ OnSlotAdded?.Invoke(newSlotData);
+ return true;
+ }
+
+ public void RemoveSlotDef(EntityUid owner, ClientInventoryComponent component, SlotData slotData)
+ {
+ if (component.SlotData.Remove(slotData.SlotName))
+ {
+ if (owner == _playerManager.LocalPlayer?.ControlledEntity)
+ OnSlotRemoved?.Invoke(slotData);
+ }
+ }
+
+ public void RemoveSlotDef(EntityUid owner, ClientInventoryComponent component, string slotName)
+ {
+ if (!component.SlotData.TryGetValue(slotName, out var slotData))
+ return;
+
+ component.SlotData.Remove(slotName);
+
+ if (owner == _playerManager.LocalPlayer?.ControlledEntity)
+ OnSlotRemoved?.Invoke(slotData);
+ }
+
+ // TODO hud refactor This should also live in a UI Controller
+ private void HoverInSlotButton(EntityUid uid, string slot, SlotControl control,
+ InventoryComponent? inventoryComponent = null, SharedHandsComponent? hands = null)
{
if (!Resolve(uid, ref inventoryComponent))
return;
@@ -199,160 +225,101 @@ namespace Content.Client.Inventory
if (hands.ActiveHandEntity is not EntityUid heldEntity)
return;
- if(!TryGetSlotContainer(uid, slot, out var containerSlot, out var slotDef, inventoryComponent))
+ if (!TryGetSlotContainer(uid, slot, out var containerSlot, out var slotDef, inventoryComponent))
return;
-
- _itemSlotManager.HoverInSlot(button, heldEntity,
- CanEquip(uid, heldEntity, slot, out _, slotDef, inventoryComponent) &&
- containerSlot.CanInsert(heldEntity, EntityManager));
}
- private void HandleSlotButtonPressed(EntityUid uid, string slot, ItemSlotButton button,
- GUIBoundKeyEventArgs args)
+ public void UIInventoryActivate(string slot)
{
- if (TryGetSlotEntity(uid, slot, out var itemUid) && _itemSlotManager.OnButtonPressed(args, itemUid.Value))
- return;
-
- if (args.Function != EngineKeyFunctions.UIClick)
- return;
-
- // only raise event if either itemUid is not null, or the user is holding something
- if (itemUid != null || TryComp(uid, out SharedHandsComponent? hands) && hands.ActiveHandEntity != null)
- EntityManager.RaisePredictiveEvent(new UseSlotNetworkMessage(slot));
+ EntityManager.RaisePredictiveEvent(new UseSlotNetworkMessage(slot));
}
- private bool TryGetUIElements(EntityUid uid, [NotNullWhen(true)] out DefaultWindow? invWindow,
- [NotNullWhen(true)] out Control? invBottomLeft, [NotNullWhen(true)] out Control? invBottomRight,
- [NotNullWhen(true)] out Control? invTopQuick, ClientInventoryComponent? component = null)
+ public void UIInventoryStorageActivate(string slot)
{
- invWindow = null;
- invBottomLeft = null;
- invBottomRight = null;
- invTopQuick = null;
+ EntityManager.RaisePredictiveEvent(new OpenSlotStorageNetworkMessage(slot));
+ }
- if (!Resolve(uid, ref component))
- return false;
+ public void UIInventoryExamine(string slot, EntityUid uid)
+ {
+ if (!TryGetSlotEntity(uid, slot, out var item))
+ return;
- if(!_prototypeManager.TryIndex(component.TemplateId, out var template))
- return false;
+ _examine.DoExamine(item.Value);
+ }
- if (!_uiGenerateDelegates.TryGetValue(component.TemplateId, out var genfunc))
+ public void UIInventoryOpenContextMenu(string slot, EntityUid uid)
+ {
+ if (!TryGetSlotEntity(uid, slot, out var item))
+ return;
+
+ _verbs.VerbMenu.OpenVerbMenu(item.Value);
+ }
+
+ public void UIInventoryActivateItem(string slot, EntityUid uid)
+ {
+ if (!TryGetSlotEntity(uid, slot, out var item))
+ return;
+
+ EntityManager.EntityNetManager?.SendSystemNetworkMessage(
+ new InteractInventorySlotEvent(item.Value, altInteract: false));
+ }
+
+ public void UIInventoryAltActivateItem(string slot, EntityUid uid)
+ {
+ if (!TryGetSlotEntity(uid, slot, out var item))
+ return;
+
+ EntityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(item.Value, altInteract: true));
+ }
+
+ public sealed class SlotData
+ {
+ public readonly SlotDefinition SlotDef;
+ public EntityUid? HeldEntity => Container?.ContainedEntity;
+ public bool Blocked;
+ public bool Highlighted;
+ public ContainerSlot? Container;
+ public bool HasSlotGroup => SlotDef.SlotGroup != "Default";
+ public Vector2i ButtonOffset => SlotDef.UIWindowPosition;
+ public string SlotName => SlotDef.Name;
+ public bool ShowInWindow => SlotDef.ShowInWindow;
+ public string SlotGroup => SlotDef.SlotGroup;
+ public string SlotDisplayName => SlotDef.DisplayName;
+ public string TextureName => "Slots/" + SlotDef.TextureName;
+
+ public SlotData(SlotDefinition slotDef, ContainerSlot? container = null, bool highlighted = false,
+ bool blocked = false)
{
- _uiGenerateDelegates[component.TemplateId] = genfunc = (entityUid, list) =>
- {
- var window = new DefaultWindow()
- {
- Title = Loc.GetString("human-inventory-window-title"),
- Resizable = false
- };
- window.OnClose += () =>
- {
- _gameHud.InventoryButtonDown = false;
- _gameHud.TopInventoryQuickButtonContainer.Visible = false;
- };
- var windowContents = new LayoutContainer
- {
- MinSize = (ButtonSize * 4 + ButtonSeparation * 3 + RightSeparation,
- ButtonSize * 4 + ButtonSeparation * 3)
- };
- window.Contents.AddChild(windowContents);
-
- ItemSlotButton GetButton(SlotDefinition definition, string textureBack)
- {
- var btn = new ItemSlotButton(ButtonSize, $"{definition.TextureName}.png", textureBack,
- _gameHud)
- {
- OnStoragePressed = (e) =>
- {
- if (e.Function != EngineKeyFunctions.UIClick &&
- e.Function != ContentKeyFunctions.ActivateItemInWorld)
- return;
- RaiseNetworkEvent(new OpenSlotStorageNetworkMessage(definition.Name));
- }
- };
- btn.OnHover = (_) =>
- {
- HoverInSlotButton(entityUid, definition.Name, btn);
- };
- btn.OnPressed = (e) =>
- {
- HandleSlotButtonPressed(entityUid, definition.Name, btn, e);
- };
- return btn;
- }
-
- void AddButton(SlotDefinition definition, Vector2i position)
- {
- var button = GetButton(definition, "back.png");
- LayoutContainer.SetPosition(button, position);
- windowContents.AddChild(button);
- if (!list.ContainsKey(definition.Name))
- list[definition.Name] = new();
- list[definition.Name].Add(button);
- }
-
- void AddHUDButton(BoxContainer container, SlotDefinition definition)
- {
- var button = GetButton(definition, "back.png");
- container.AddChild(button);
- if (!list.ContainsKey(definition.Name))
- list[definition.Name] = new();
- list[definition.Name].Add(button);
- }
-
- var topQuick = new BoxContainer
- {
- Orientation = BoxContainer.LayoutOrientation.Horizontal,
- SeparationOverride = 5
- };
- var bottomRight = new BoxContainer
- {
- Orientation = BoxContainer.LayoutOrientation.Horizontal,
- SeparationOverride = 5
- };
- var bottomLeft = new BoxContainer
- {
- Orientation = BoxContainer.LayoutOrientation.Horizontal,
- SeparationOverride = 5
- };
-
- const int sizep = (ButtonSize + ButtonSeparation);
-
- foreach (var slotDefinition in template.Slots)
- {
- switch (slotDefinition.UIContainer)
- {
- case SlotUIContainer.BottomLeft:
- AddHUDButton(bottomLeft, slotDefinition);
- break;
- case SlotUIContainer.BottomRight:
- AddHUDButton(bottomRight, slotDefinition);
- break;
- case SlotUIContainer.Top:
- AddHUDButton(topQuick, slotDefinition);
- break;
- }
-
- AddButton(slotDefinition, slotDefinition.UIWindowPosition * sizep);
- }
-
- return (window, bottomLeft, bottomRight, topQuick);
- };
+ SlotDef = slotDef;
+ Highlighted = highlighted;
+ Blocked = blocked;
+ Container = container;
}
- var res = genfunc(uid, component.SlotButtons);
- invWindow = res.window;
- invBottomLeft = res.bottomLeft;
- invBottomRight = res.bottomRight;
- invTopQuick = res.topQuick;
- return true;
+ public SlotData(SlotData oldData, bool highlighted = false, bool blocked = false)
+ {
+ SlotDef = oldData.SlotDef;
+ Highlighted = highlighted;
+ Container = oldData.Container;
+ Blocked = blocked;
+ }
+
+ public static implicit operator SlotData(SlotDefinition s)
+ {
+ return new SlotData(s);
+ }
+
+ public static implicit operator SlotDefinition(SlotData s)
+ {
+ return s.SlotDef;
+ }
}
-
- private void HandleOpenInventoryMenu()
- {
- _gameHud.InventoryButtonDown = !_gameHud.InventoryButtonDown;
- _gameHud.TopInventoryQuickButtonContainer.Visible = _gameHud.InventoryButtonDown;
- }
+ public readonly record struct SlotSpriteUpdate(
+ string Group,
+ string Name,
+ ISpriteComponent? Sprite,
+ bool ShowStorage
+ );
}
}
diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs
index cde59b9268..54e40f2995 100644
--- a/Content.Client/IoC/ClientContentIoC.cs
+++ b/Content.Client/IoC/ClientContentIoC.cs
@@ -5,9 +5,7 @@ using Content.Client.Clickable;
using Content.Client.Options;
using Content.Client.Eui;
using Content.Client.GhostKick;
-using Content.Client.HUD;
using Content.Client.Info;
-using Content.Client.Items.Managers;
using Content.Client.Launcher;
using Content.Client.Module;
using Content.Client.Parallax.Managers;
@@ -27,12 +25,10 @@ namespace Content.Client.IoC
{
public static void Register()
{
- IoCManager.Register();
IoCManager.Register();
IoCManager.Register();
IoCManager.Register();
IoCManager.Register();
- IoCManager.Register();
IoCManager.Register();
IoCManager.Register();
IoCManager.Register();
diff --git a/Content.Client/Items/Managers/IItemSlotManager.cs b/Content.Client/Items/Managers/IItemSlotManager.cs
deleted file mode 100644
index 43ea8b4eb8..0000000000
--- a/Content.Client/Items/Managers/IItemSlotManager.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System;
-using Content.Client.Items.UI;
-using Robust.Client.UserInterface;
-using Robust.Shared.GameObjects;
-
-namespace Content.Client.Items.Managers
-{
- public interface IItemSlotManager
- {
- bool OnButtonPressed(GUIBoundKeyEventArgs args, EntityUid? item);
- void UpdateCooldown(ItemSlotButton? cooldownTexture, EntityUid? entity);
- bool SetItemSlot(ItemSlotButton button, EntityUid? entity);
- void HoverInSlot(ItemSlotButton button, EntityUid? entity, bool fits);
- event Action? EntityHighlightedUpdated;
- bool IsHighlighted(EntityUid? uid);
-
- ///
- /// Highlight all slot controls that contain the specified entity.
- ///
- /// The UID of the entity to highlight.
- ///
- void HighlightEntity(EntityUid uid);
-
- ///
- /// Remove highlighting for the specified entity.
- ///
- /// The UID of the entity to unhighlight.
- ///
- void UnHighlightEntity(EntityUid uid);
- }
-}
diff --git a/Content.Client/Items/Managers/ItemSlotManager.cs b/Content.Client/Items/Managers/ItemSlotManager.cs
deleted file mode 100644
index 323fa228a8..0000000000
--- a/Content.Client/Items/Managers/ItemSlotManager.cs
+++ /dev/null
@@ -1,179 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Content.Client.Examine;
-using Content.Client.Items.UI;
-using Content.Client.Storage;
-using Content.Client.Verbs;
-using Content.Shared.Cooldown;
-using Content.Shared.Hands.Components;
-using Content.Shared.Input;
-using Content.Shared.Interaction;
-using Robust.Client.GameObjects;
-using Robust.Client.UserInterface;
-using Robust.Shared.GameObjects;
-using Robust.Shared.Input;
-using Robust.Shared.IoC;
-using Robust.Shared.Map;
-using Robust.Shared.Maths;
-using Robust.Shared.Timing;
-
-namespace Content.Client.Items.Managers
-{
- public sealed class ItemSlotManager : IItemSlotManager
- {
- [Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
- [Dependency] private readonly IEntityManager _entityManager = default!;
-
- private readonly HashSet _highlightEntities = new();
-
- public event Action? EntityHighlightedUpdated;
-
- public bool SetItemSlot(ItemSlotButton button, EntityUid? entity)
- {
- if (entity == null)
- {
- button.SpriteView.Sprite = null;
- button.StorageButton.Visible = false;
- }
- else
- {
- ISpriteComponent? sprite;
- if (_entityManager.TryGetComponent(entity, out HandVirtualItemComponent? virtPull)
- && _entityManager.TryGetComponent(virtPull.BlockingEntity, out ISpriteComponent? pulledSprite))
- {
- sprite = pulledSprite;
- }
- else if (!_entityManager.TryGetComponent(entity, out sprite))
- {
- return false;
- }
-
- button.ClearHover();
- button.SpriteView.Sprite = sprite;
- button.StorageButton.Visible = _entityManager.HasComponent(entity);
- }
-
- button.Entity = entity ?? default;
-
- // im lazy
- button.UpdateSlotHighlighted();
- return true;
- }
-
- public bool OnButtonPressed(GUIBoundKeyEventArgs args, EntityUid? item)
- {
- if (item == null)
- return false;
-
- if (args.Function == ContentKeyFunctions.ExamineEntity)
- {
- _entitySystemManager.GetEntitySystem()
- .DoExamine(item.Value);
- }
- else if (args.Function == EngineKeyFunctions.UseSecondary)
- {
- _entitySystemManager.GetEntitySystem().VerbMenu.OpenVerbMenu(item.Value);
- }
- else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
- {
- _entityManager.EntityNetManager?.SendSystemNetworkMessage(new InteractInventorySlotEvent(item.Value, altInteract: false));
- }
- else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld)
- {
- _entityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(item.Value, altInteract: true));
- }
- else
- {
- return false;
- }
- args.Handle();
- return true;
- }
-
- public void UpdateCooldown(ItemSlotButton? button, EntityUid? entity)
- {
- var cooldownDisplay = button?.CooldownDisplay;
-
- if (cooldownDisplay == null)
- {
- return;
- }
-
- if (entity == null || _entityManager.Deleted(entity) ||
- !_entityManager.TryGetComponent(entity, out ItemCooldownComponent? cooldown) ||
- !cooldown.CooldownStart.HasValue ||
- !cooldown.CooldownEnd.HasValue)
- {
- cooldownDisplay.Visible = false;
- return;
- }
-
- var start = cooldown.CooldownStart.Value;
- var end = cooldown.CooldownEnd.Value;
-
- var length = (end - start).TotalSeconds;
- var progress = (_gameTiming.CurTime - start).TotalSeconds / length;
- var ratio = (progress <= 1 ? (1 - progress) : (_gameTiming.CurTime - end).TotalSeconds * -5);
-
- cooldownDisplay.Progress = MathHelper.Clamp((float) ratio, -1, 1);
- cooldownDisplay.Visible = ratio > -1f;
- }
-
- public void HoverInSlot(ItemSlotButton button, EntityUid? entity, bool fits)
- {
- if (entity == null || !button.MouseIsHovering)
- {
- button.ClearHover();
- return;
- }
-
- if (!_entityManager.HasComponent(entity))
- {
- return;
- }
-
- // Set green / red overlay at 50% transparency
- var hoverEntity = _entityManager.SpawnEntity("hoverentity", MapCoordinates.Nullspace);
- var hoverSprite = _entityManager.GetComponent(hoverEntity);
- hoverSprite.CopyFrom(_entityManager.GetComponent(entity.Value));
- hoverSprite.Color = fits ? new Color(0, 255, 0, 127) : new Color(255, 0, 0, 127);
-
- button.HoverSpriteView.Sprite = hoverSprite;
- }
-
- public bool IsHighlighted(EntityUid? uid)
- {
- if (uid == null) return false;
- return _highlightEntities.Contains(uid.Value);
- }
-
- public void HighlightEntity(EntityUid uid)
- {
- if (!_highlightEntities.Add(uid))
- return;
-
- EntityHighlightedUpdated?.Invoke(new EntitySlotHighlightedEventArgs(uid, true));
- }
-
- public void UnHighlightEntity(EntityUid uid)
- {
- if (!_highlightEntities.Remove(uid))
- return;
-
- EntityHighlightedUpdated?.Invoke(new EntitySlotHighlightedEventArgs(uid, false));
- }
- }
-
- public readonly struct EntitySlotHighlightedEventArgs
- {
- public EntitySlotHighlightedEventArgs(EntityUid entity, bool newHighlighted)
- {
- Entity = entity;
- NewHighlighted = newHighlighted;
- }
-
- public EntityUid Entity { get; }
- public bool NewHighlighted { get; }
- }
-}
diff --git a/Content.Client/Items/UI/ItemSlotButton.cs b/Content.Client/Items/UI/ItemSlotButton.cs
deleted file mode 100644
index e7e41a4296..0000000000
--- a/Content.Client/Items/UI/ItemSlotButton.cs
+++ /dev/null
@@ -1,197 +0,0 @@
-using System;
-using Content.Client.Cooldown;
-using Content.Client.HUD;
-using Content.Client.Items.Managers;
-using Content.Client.Stylesheets;
-using Robust.Client.GameObjects;
-using Robust.Client.Graphics;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.Utility;
-using Robust.Shared.GameObjects;
-using Robust.Shared.Input;
-using Robust.Shared.IoC;
-using Robust.Shared.Maths;
-using Robust.Shared.Utility;
-
-namespace Content.Client.Items.UI
-{
- [Virtual]
- public class ItemSlotButton : Control, IEntityEventSubscriber
- {
- private const string HighlightShader = "SelectionOutlineInrange";
-
- [Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
-
- public EntityUid? Entity { get; set; }
- public TextureRect Button { get; }
- public SpriteView SpriteView { get; }
- public SpriteView HoverSpriteView { get; }
- public TextureButton StorageButton { get; }
- public CooldownGraphic CooldownDisplay { get; }
-
- public Action? OnPressed { get; set; }
- public Action? OnStoragePressed { get; set; }
- public Action? OnHover { get; set; }
-
- public bool EntityHover => HoverSpriteView.Sprite != null;
- public bool MouseIsHovering;
-
- private readonly PanelContainer _highlightRect;
-
- private string _textureName;
- private string _storageTextureName;
-
- public ItemSlotButton(int size, string textureName, string storageTextureName, IGameHud gameHud)
- {
- _textureName = textureName;
- _storageTextureName = storageTextureName;
- IoCManager.InjectDependencies(this);
-
- MinSize = (size, size);
-
- AddChild(Button = new TextureRect
- {
- TextureScale = (2, 2),
- MouseFilter = MouseFilterMode.Stop
- });
-
- AddChild(_highlightRect = new PanelContainer
- {
- StyleClasses = { StyleNano.StyleClassHandSlotHighlight },
- MinSize = (32, 32),
- Visible = false
- });
-
- Button.OnKeyBindDown += OnButtonPressed;
-
- AddChild(SpriteView = new SpriteView
- {
- Scale = (2, 2),
- OverrideDirection = Direction.South
- });
-
- AddChild(HoverSpriteView = new SpriteView
- {
- Scale = (2, 2),
- OverrideDirection = Direction.South
- });
-
- AddChild(StorageButton = new TextureButton
- {
- Scale = (0.75f, 0.75f),
- HorizontalAlignment = HAlignment.Right,
- VerticalAlignment = VAlignment.Bottom,
- Visible = false,
- });
-
- StorageButton.OnKeyBindDown += args =>
- {
- if (args.Function != EngineKeyFunctions.UIClick)
- {
- OnButtonPressed(args);
- }
- };
-
- StorageButton.OnPressed += OnStorageButtonPressed;
-
- Button.OnMouseEntered += _ =>
- {
- MouseIsHovering = true;
- };
- Button.OnMouseEntered += OnButtonHover;
-
- Button.OnMouseExited += _ =>
- {
- MouseIsHovering = false;
- ClearHover();
- };
-
- AddChild(CooldownDisplay = new CooldownGraphic
- {
- Visible = false,
- });
-
- RefreshTextures(gameHud);
- }
-
- public void RefreshTextures(IGameHud gameHud)
- {
- Button.Texture = gameHud.GetHudTexture(_textureName);
- StorageButton.TextureNormal = gameHud.GetHudTexture(_storageTextureName);
- }
-
- protected override void EnteredTree()
- {
- base.EnteredTree();
-
- _itemSlotManager.EntityHighlightedUpdated += HandleEntitySlotHighlighted;
- UpdateSlotHighlighted();
- }
-
- protected override void ExitedTree()
- {
- base.ExitedTree();
-
- _itemSlotManager.EntityHighlightedUpdated -= HandleEntitySlotHighlighted;
- }
-
- private void HandleEntitySlotHighlighted(EntitySlotHighlightedEventArgs entitySlotHighlightedEventArgs)
- {
- UpdateSlotHighlighted();
- }
-
- public void UpdateSlotHighlighted()
- {
- Highlight(_itemSlotManager.IsHighlighted(Entity));
- }
-
- public void ClearHover()
- {
- if (EntityHover)
- {
- ISpriteComponent? tempQualifier = HoverSpriteView.Sprite;
- if (tempQualifier != null)
- {
- IoCManager.Resolve().DeleteEntity(tempQualifier.Owner);
- }
-
- HoverSpriteView.Sprite = null;
- }
- }
-
- public virtual void Highlight(bool highlight)
- {
- if (highlight)
- {
- _highlightRect.Visible = true;
- }
- else
- {
- _highlightRect.Visible = false;
- }
- }
-
- private void OnButtonPressed(GUIBoundKeyEventArgs args)
- {
- OnPressed?.Invoke(args);
- }
-
- private void OnStorageButtonPressed(BaseButton.ButtonEventArgs args)
- {
- if (args.Event.Function == EngineKeyFunctions.UIClick)
- {
- OnStoragePressed?.Invoke(args.Event);
- }
- else
- {
- OnPressed?.Invoke(args.Event);
- }
- }
-
- private void OnButtonHover(GUIMouseHoverEventArgs args)
- {
- OnHover?.Invoke(args);
- }
- }
-}
diff --git a/Content.Client/Items/UI/ItemStatusPanel.cs b/Content.Client/Items/UI/ItemStatusPanel.cs
deleted file mode 100644
index 31afcbd36d..0000000000
--- a/Content.Client/Items/UI/ItemStatusPanel.cs
+++ /dev/null
@@ -1,182 +0,0 @@
-using Content.Client.Resources;
-using Content.Client.Stylesheets;
-using Content.Shared.Hands.Components;
-using Content.Shared.IdentityManagement;
-using Robust.Client.Graphics;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Timing;
-using Robust.Shared.Utility;
-using static Content.Client.IoC.StaticIoC;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
-
-namespace Content.Client.Items.UI
-{
- public sealed class ItemStatusPanel : Control
- {
- [Dependency] private readonly IEntityManager _entityManager = default!;
-
- [ViewVariables]
- private readonly Label _itemNameLabel;
- [ViewVariables]
- private readonly BoxContainer _statusContents;
- [ViewVariables]
- private readonly PanelContainer _panel;
-
- [ViewVariables]
- private EntityUid? _entity;
-
- public ItemStatusPanel(Texture texture, StyleBox.Margin cutout, StyleBox.Margin flat, Label.AlignMode textAlign)
- {
- IoCManager.InjectDependencies(this);
-
- var panel = new StyleBoxTexture
- {
- Texture = texture
- };
- panel.SetContentMarginOverride(StyleBox.Margin.Vertical, 4);
- panel.SetContentMarginOverride(StyleBox.Margin.Horizontal, 6);
- panel.SetPatchMargin(flat, 2);
- panel.SetPatchMargin(cutout, 13);
-
- AddChild(_panel = new PanelContainer
- {
- PanelOverride = panel,
- ModulateSelfOverride = Color.White.WithAlpha(0.9f),
- Children =
- {
- new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- SeparationOverride = 0,
- Children =
- {
- (_statusContents = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical
- }),
- (_itemNameLabel = new Label
- {
- ClipText = true,
- StyleClasses = {StyleNano.StyleClassItemStatus},
- Align = textAlign
- })
- }
- }
- }
- });
- VerticalAlignment = VAlignment.Bottom;
-
- // TODO: Depending on if its a two-hand panel or not
- MinSize = (150, 0);
- }
-
- ///