Action container rejig (#20260)

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
Leon Friedrich
2023-09-23 04:49:39 -04:00
committed by GitHub
parent c80c90ed65
commit 684b334806
50 changed files with 889 additions and 740 deletions

View File

@@ -4,7 +4,6 @@ using Content.Shared.Actions;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Shared.Containers;
using Robust.Shared.ContentPack;
using Robust.Shared.GameStates;
using Robust.Shared.Input.Binding;
@@ -28,8 +27,8 @@ namespace Content.Client.Actions
[Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
public event Action<EntityUid>? ActionAdded;
public event Action<EntityUid>? ActionRemoved;
public event Action<EntityUid>? OnActionAdded;
public event Action<EntityUid>? OnActionRemoved;
public event OnActionReplaced? ActionReplaced;
public event Action? ActionsUpdated;
public event Action<ActionsComponent>? LinkActions;
@@ -37,11 +36,8 @@ namespace Content.Client.Actions
public event Action? ClearAssignments;
public event Action<List<SlotAssignment>>? AssignSlot;
/// <summary>
/// Queue of entities with <see cref="ActionsComponent"/> that needs to be updated after
/// handling a state.
/// </summary>
private readonly Queue<EntityUid> _actionHoldersQueue = new();
private readonly List<EntityUid> _removed = new();
private readonly List<(EntityUid, BaseActionComponent?)> _added = new();
public override void Initialize()
{
@@ -49,15 +45,73 @@ namespace Content.Client.Actions
SubscribeLocalEvent<ActionsComponent, PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<ActionsComponent, PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<ActionsComponent, ComponentHandleState>(HandleComponentState);
SubscribeLocalEvent<InstantActionComponent, ComponentHandleState>(OnInstantHandleState);
SubscribeLocalEvent<EntityTargetActionComponent, ComponentHandleState>(OnEntityTargetHandleState);
SubscribeLocalEvent<WorldTargetActionComponent, ComponentHandleState>(OnWorldTargetHandleState);
}
public override void Dirty(EntityUid? actionId)
private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
{
var action = GetActionData(actionId);
if (_playerManager.LocalPlayer?.ControlledEntity != action?.AttachedEntity)
if (args.Current is not InstantActionComponentState state)
return;
BaseHandleState<InstantActionComponent>(uid, component, state);
}
private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponent component, ref ComponentHandleState args)
{
if (args.Current is not EntityTargetActionComponentState state)
return;
component.Whitelist = state.Whitelist;
component.CanTargetSelf = state.CanTargetSelf;
BaseHandleState<EntityTargetActionComponent>(uid, component, state);
}
private void OnWorldTargetHandleState(EntityUid uid, WorldTargetActionComponent component, ref ComponentHandleState args)
{
if (args.Current is not WorldTargetActionComponentState state)
return;
BaseHandleState<WorldTargetActionComponent>(uid, component, state);
}
private void BaseHandleState<T>(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent
{
component.Icon = state.Icon;
component.IconOn = state.IconOn;
component.IconColor = state.IconColor;
component.Keywords = new HashSet<string>(state.Keywords);
component.Enabled = state.Enabled;
component.Toggled = state.Toggled;
component.Cooldown = state.Cooldown;
component.UseDelay = state.UseDelay;
component.Charges = state.Charges;
component.Container = EnsureEntity<T>(state.Container, uid);
component.EntityIcon = EnsureEntity<T>(state.EntityIcon, uid);
component.CheckCanInteract = state.CheckCanInteract;
component.ClientExclusive = state.ClientExclusive;
component.Priority = state.Priority;
component.AttachedEntity = EnsureEntity<T>(state.AttachedEntity, uid);
component.AutoPopulate = state.AutoPopulate;
component.Temporary = state.Temporary;
component.ItemIconStyle = state.ItemIconStyle;
component.Sound = state.Sound;
if (_playerManager.LocalPlayer?.ControlledEntity == component.AttachedEntity)
ActionsUpdated?.Invoke();
}
protected override void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null)
{
if (!ResolveActionData(actionId, ref action))
return;
base.UpdateAction(actionId, action);
if (_playerManager.LocalPlayer?.ControlledEntity != action.AttachedEntity)
return;
base.Dirty(actionId);
ActionsUpdated?.Invoke();
}
@@ -66,70 +120,73 @@ namespace Content.Client.Actions
if (args.Current is not ActionsComponentState state)
return;
component.Actions.Clear();
component.Actions.UnionWith(EnsureEntitySet<ActionsComponent>(state.Actions, uid));
_actionHoldersQueue.Enqueue(uid);
}
protected override void AddActionInternal(EntityUid holderId, EntityUid actionId, BaseContainer container, ActionsComponent holder)
{
// 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 (container.Contains(actionId))
_added.Clear();
_removed.Clear();
var stateEnts = EnsureEntitySet<ActionsComponent>(state.Actions, uid);
foreach (var act in component.Actions)
{
ActionReplaced?.Invoke(actionId);
if (!stateEnts.Contains(act) && !IsClientSide(act))
_removed.Add(act);
}
else
{
base.AddActionInternal(holderId, actionId, container, holder);
}
}
component.Actions.ExceptWith(_removed);
public override void AddAction(EntityUid holderId, EntityUid actionId, EntityUid? provider, ActionsComponent? holder = null, BaseActionComponent? action = null, bool dirty = true, BaseContainer? actionContainer = null)
{
if (!Resolve(holderId, ref holder, false))
return;
action ??= GetActionData(actionId);
if (action == null)
foreach (var actionId in stateEnts)
{
Log.Warning($"No {nameof(BaseActionComponent)} found on entity {actionId}");
return;
if (!actionId.IsValid())
continue;
if (!component.Actions.Add(actionId))
continue;
TryGetActionData(actionId, out var action);
_added.Add((actionId, action));
}
dirty &= !action.ClientExclusive;
base.AddAction(holderId, actionId, provider, holder, action, dirty, actionContainer);
if (_playerManager.LocalPlayer?.ControlledEntity != uid)
return;
if (holderId == _playerManager.LocalPlayer?.ControlledEntity)
ActionAdded?.Invoke(actionId);
foreach (var action in _removed)
{
OnActionRemoved?.Invoke(action);
}
_added.Sort(ActionComparer);
foreach (var action in _added)
{
OnActionAdded?.Invoke(action.Item1);
}
ActionsUpdated?.Invoke();
}
public override void RemoveAction(EntityUid holderId, EntityUid? actionId, ActionsComponent? comp = null, BaseActionComponent? action = null, bool dirty = true)
public static int ActionComparer((EntityUid, BaseActionComponent?) a, (EntityUid, BaseActionComponent?) b)
{
if (GameTiming.ApplyingState)
var priorityA = a.Item2?.Priority ?? 0;
var priorityB = b.Item2?.Priority ?? 0;
if (priorityA != priorityB)
return priorityA - priorityB;
priorityA = a.Item2?.Container?.Id ?? 0;
priorityB = b.Item2?.Container?.Id ?? 0;
return priorityA - priorityB;
}
protected override void ActionAdded(EntityUid performer, EntityUid actionId, ActionsComponent comp,
BaseActionComponent action)
{
if (_playerManager.LocalPlayer?.ControlledEntity != performer)
return;
if (!Resolve(holderId, ref comp, false))
OnActionAdded?.Invoke(actionId);
}
protected override void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action)
{
if (_playerManager.LocalPlayer?.ControlledEntity != performer)
return;
if (actionId == null)
return;
action ??= GetActionData(actionId);
if (action is { ClientExclusive: false })
return;
dirty &= !action?.ClientExclusive ?? true;
base.RemoveAction(holderId, actionId, comp, action, dirty);
if (_playerManager.LocalPlayer?.ControlledEntity != holderId)
return;
if (action == null || action.AutoRemove)
ActionRemoved?.Invoke(actionId.Value);
OnActionRemoved?.Invoke(actionId);
}
public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetClientActions()
@@ -180,9 +237,6 @@ namespace Content.Client.Actions
return;
}
if (action.Provider != null && Deleted(action.Provider))
return;
if (action is not InstantActionComponent instantAction)
return;
@@ -233,8 +287,8 @@ namespace Content.Client.Actions
var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true);
var actionId = Spawn(null);
AddComp<Component>(actionId, action);
AddAction(user, actionId, null);
AddComp(actionId, action);
AddActionDirect(user, actionId);
if (map.TryGet<ValueDataNode>("name", out var nameNode))
_metaData.SetEntityName(actionId, nameNode.Value);
@@ -254,95 +308,6 @@ namespace Content.Client.Actions
AssignSlot?.Invoke(assignments);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
if (_actionHoldersQueue.Count == 0)
return;
var removed = new List<EntityUid>();
var added = new List<(EntityUid Id, BaseActionComponent Comp)>();
var query = GetEntityQuery<ActionsComponent>();
var queue = new Queue<EntityUid>(_actionHoldersQueue);
_actionHoldersQueue.Clear();
while (queue.TryDequeue(out var holderId))
{
if (!TryGetContainer(holderId, out var container) || container.ExpectedEntities.Count > 0)
{
_actionHoldersQueue.Enqueue(holderId);
continue;
}
if (!query.TryGetComponent(holderId, out var holder))
continue;
removed.Clear();
added.Clear();
foreach (var (act, data) in holder.OldClientActions.ToList())
{
if (data.ClientExclusive)
continue;
if (!holder.Actions.Contains(act))
{
holder.OldClientActions.Remove(act);
if (data.AutoRemove)
removed.Add(act);
}
}
// Anything that remains is a new action
foreach (var newAct in holder.Actions)
{
if (!TryGetActionData(newAct, out var serverData))
continue;
if (!holder.OldClientActions.ContainsKey(newAct))
added.Add((newAct, serverData));
holder.OldClientActions[newAct] = new ActionMetaData(serverData.ClientExclusive, serverData.AutoRemove);
}
if (_playerManager.LocalPlayer?.ControlledEntity != holderId)
return;
foreach (var action in removed)
{
ActionRemoved?.Invoke(action);
}
added.Sort(static (a, b) =>
{
if (a.Comp.Priority != b.Comp.Priority)
return a.Comp.Priority - b.Comp.Priority;
if (a.Comp.Provider != b.Comp.Provider)
{
if (a.Comp.Provider == null)
return -1;
if (b.Comp.Provider == null)
return 1;
// uid to int casting... it says "Do NOT use this in content". You can't tell me what to do.
return (int) a.Comp.Provider - (int) b.Comp.Provider;
}
return 0;
});
foreach (var action in added)
{
ActionAdded?.Invoke(action.Item1);
}
ActionsUpdated?.Invoke();
}
}
public record struct SlotAssignment(byte Hotbar, byte Slot, EntityUid ActionId);
}
}

View File

@@ -55,7 +55,7 @@ namespace Content.Client.Ghost
{
base.Initialize();
SubscribeLocalEvent<GhostComponent, ComponentInit>(OnGhostInit);
SubscribeLocalEvent<GhostComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<GhostComponent, ComponentRemove>(OnGhostRemove);
SubscribeLocalEvent<GhostComponent, AfterAutoHandleStateEvent>(OnGhostState);
@@ -72,16 +72,10 @@ namespace Content.Client.Ghost
SubscribeLocalEvent<GhostComponent, ToggleGhostsActionEvent>(OnToggleGhosts);
}
private void OnGhostInit(EntityUid uid, GhostComponent component, ComponentInit args)
private void OnStartup(EntityUid uid, GhostComponent component, ComponentStartup args)
{
if (TryComp(uid, out SpriteComponent? sprite))
{
sprite.Visible = GhostVisibility;
}
_actions.AddAction(uid, ref component.ToggleLightingActionEntity, component.ToggleGhostsAction);
_actions.AddAction(uid, ref component.ToggleFoVActionEntity, component.ToggleFoVAction);
_actions.AddAction(uid, ref component.ToggleGhostsActionEntity, component.ToggleGhostsAction);
}
private void OnToggleLighting(EntityUid uid, GhostComponent component, ToggleLightingActionEvent args)

View File

@@ -14,7 +14,8 @@ public sealed class NetworkConfiguratorLinkOverlay : Overlay
[Dependency] private readonly IRobustRandom _random = default!;
private readonly DeviceListSystem _deviceListSystem;
private Dictionary<EntityUid, Color> _colors = new();
public Dictionary<EntityUid, Color> Colors = new();
public EntityUid? Action;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
@@ -25,11 +26,6 @@ public sealed class NetworkConfiguratorLinkOverlay : Overlay
_deviceListSystem = _entityManager.System<DeviceListSystem>();
}
public void ClearEntity(EntityUid uid)
{
_colors.Remove(uid);
}
protected override void Draw(in OverlayDrawArgs args)
{
foreach (var tracker in _entityManager.EntityQuery<NetworkConfiguratorActiveLinkOverlayComponent>())
@@ -40,13 +36,13 @@ public sealed class NetworkConfiguratorLinkOverlay : Overlay
continue;
}
if (!_colors.TryGetValue(tracker.Owner, out var color))
if (!Colors.TryGetValue(tracker.Owner, out var color))
{
color = new Color(
_random.Next(0, 255),
_random.Next(0, 255),
_random.Next(0, 255));
_colors.Add(tracker.Owner, color);
Colors.Add(tracker.Owner, color);
}
var sourceTransform = _entityManager.GetComponent<TransformComponent>(tracker.Owner);
@@ -70,7 +66,7 @@ public sealed class NetworkConfiguratorLinkOverlay : Overlay
continue;
}
args.WorldHandle.DrawLine(sourceTransform.WorldPosition, linkTransform.WorldPosition, _colors[tracker.Owner]);
args.WorldHandle.DrawLine(sourceTransform.WorldPosition, linkTransform.WorldPosition, Colors[tracker.Owner]);
}
}
}

View File

@@ -3,6 +3,7 @@ using Content.Client.Actions;
using Content.Client.Items;
using Content.Client.Message;
using Content.Client.Stylesheets;
using Content.Shared.Actions;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Systems;
using Content.Shared.Input;
@@ -61,26 +62,26 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
if (!toggle)
{
if (_overlay.HasOverlay<NetworkConfiguratorLinkOverlay>())
{
_overlay.GetOverlay<NetworkConfiguratorLinkOverlay>().ClearEntity(component.ActiveDeviceList.Value);
}
RemComp<NetworkConfiguratorActiveLinkOverlayComponent>(component.ActiveDeviceList.Value);
if (!EntityQuery<NetworkConfiguratorActiveLinkOverlayComponent>().Any())
{
_overlay.RemoveOverlay<NetworkConfiguratorLinkOverlay>();
_actions.RemoveAction(_playerManager.LocalPlayer.ControlledEntity.Value, Action);
}
if (!_overlay.TryGetOverlay(out NetworkConfiguratorLinkOverlay? overlay))
return;
overlay.Colors.Remove(component.ActiveDeviceList.Value);
if (overlay.Colors.Count > 0)
return;
_actions.RemoveAction(overlay.Action);
_overlay.RemoveOverlay<NetworkConfiguratorLinkOverlay>();
return;
}
if (!_overlay.HasOverlay<NetworkConfiguratorLinkOverlay>())
{
_overlay.AddOverlay(new NetworkConfiguratorLinkOverlay());
_actions.AddAction(_playerManager.LocalPlayer.ControlledEntity.Value, Spawn(Action), null);
var overlay = new NetworkConfiguratorLinkOverlay();
_overlay.AddOverlay(overlay);
var player = _playerManager.LocalPlayer.ControlledEntity.Value;
overlay.Action = Spawn(Action);
_actions.AddActionDirect(player, overlay.Action.Value);
}
EnsureComp<NetworkConfiguratorActiveLinkOverlayComponent>(component.ActiveDeviceList.Value);
@@ -88,7 +89,7 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
public void ClearAllOverlays()
{
if (!_overlay.HasOverlay<NetworkConfiguratorLinkOverlay>())
if (!_overlay.TryGetOverlay(out NetworkConfiguratorLinkOverlay? overlay))
{
return;
}
@@ -98,12 +99,8 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
RemCompDeferred<NetworkConfiguratorActiveLinkOverlayComponent>(tracker.Owner);
}
_overlay.RemoveOverlay<NetworkConfiguratorLinkOverlay>();
if (_playerManager.LocalPlayer?.ControlledEntity != null)
{
_actions.RemoveAction(_playerManager.LocalPlayer.ControlledEntity.Value, Action);
}
_actions.RemoveAction(overlay.Action);
_overlay.RemoveOverlay(overlay);
}
// hacky solution related to mapping

View File

@@ -111,8 +111,8 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
{
if (_actionsSystem != null)
{
_actionsSystem.ActionAdded += OnActionAdded;
_actionsSystem.ActionRemoved += OnActionRemoved;
_actionsSystem.OnActionAdded += OnActionAdded;
_actionsSystem.OnActionRemoved += OnActionRemoved;
_actionsSystem.ActionReplaced += OnActionReplaced;
_actionsSystem.ActionsUpdated += OnActionsUpdated;
}
@@ -319,8 +319,8 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
{
if (_actionsSystem != null)
{
_actionsSystem.ActionAdded -= OnActionAdded;
_actionsSystem.ActionRemoved -= OnActionRemoved;
_actionsSystem.OnActionAdded -= OnActionAdded;
_actionsSystem.OnActionRemoved -= OnActionRemoved;
_actionsSystem.ActionReplaced -= OnActionReplaced;
_actionsSystem.ActionsUpdated -= OnActionsUpdated;
}
@@ -522,8 +522,8 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
return filter switch
{
Filters.Enabled => action.Enabled,
Filters.Item => action.Provider != null && action.Provider != _playerManager.LocalPlayer?.ControlledEntity,
Filters.Innate => action.Provider == null || action.Provider == _playerManager.LocalPlayer?.ControlledEntity,
Filters.Item => action.Container != null && action.Container != _playerManager.LocalPlayer?.ControlledEntity,
Filters.Innate => action.Container == null || action.Container == _playerManager.LocalPlayer?.ControlledEntity,
Filters.Instant => action is InstantActionComponent,
Filters.Targeted => action is BaseTargetActionComponent,
_ => throw new ArgumentOutOfRangeException(nameof(filter), filter, null)
@@ -561,6 +561,9 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
if (_window is not { Disposed: false } || _actionsSystem == null)
return;
if (_playerManager.LocalPlayer?.ControlledEntity is not { } player)
return;
var search = _window.SearchBar.Text;
var filters = _window.FilterButton.SelectedKeys;
var actions = _actionsSystem.GetClientActions();
@@ -583,10 +586,10 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
if (name.Contains(search, StringComparison.OrdinalIgnoreCase))
return true;
if (action.Comp.Provider == null || action.Comp.Provider == _playerManager.LocalPlayer?.ControlledEntity)
if (action.Comp.Container == null || action.Comp.Container == player)
return false;
var providerName = EntityManager.GetComponent<MetaDataComponent>(action.Comp.Provider.Value).EntityName;
var providerName = EntityManager.GetComponent<MetaDataComponent>(action.Comp.Container.Value).EntityName;
return providerName.Contains(search, StringComparison.OrdinalIgnoreCase);
});
@@ -744,11 +747,9 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
{
if (_actionsSystem != null && _actionsSystem.TryGetActionData(_menuDragHelper.Dragged?.ActionId, out var action))
{
var entIcon = action.EntityIcon;
if (entIcon != null)
if (action.EntityIcon is {} entIcon)
{
_dragShadow.Texture = EntityManager.GetComponent<SpriteComponent>(entIcon.Value).Icon?
_dragShadow.Texture = EntityManager.GetComponent<SpriteComponent>(entIcon).Icon?
.GetFrame(RsiDirection.South, 0);
}
else if (action.Icon != null)
@@ -902,6 +903,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
return;
var actions = _actionsSystem.GetClientActions().Where(action => action.Comp.AutoPopulate).ToList();
actions.Sort(ActionComparer);
var offset = 0;
var totalPages = _pages.Count;
@@ -965,11 +967,11 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
action.Toggled = true;
// override "held-item" overlay
var provider = action.Provider;
var provider = action.Container;
if (action.TargetingIndicator && _overlays.TryGetOverlay<ShowHandItemOverlay>(out var handOverlay))
{
if (action.ItemIconStyle == ItemActionIconStyle.BigItem && action.Provider != null)
if (action.ItemIconStyle == ItemActionIconStyle.BigItem && action.Container != null)
{
handOverlay.EntityOverride = provider;
}

View File

@@ -19,14 +19,6 @@ public class ActionButtonContainer : GridContainer
public ActionButton this[int index]
{
get => (ActionButton) GetChild(index);
set
{
AddChild(value);
value.SetPositionInParent(index);
value.ActionPressed += ActionPressed;
value.ActionUnpressed += ActionUnpressed;
value.ActionFocusExited += ActionFocusExited;
}
}
public void SetActionData(params EntityUid?[] actionTypes)
@@ -63,6 +55,16 @@ public class ActionButtonContainer : GridContainer
button.ActionFocusExited += ActionFocusExited;
}
protected override void ChildRemoved(Control newChild)
{
if (newChild is not ActionButton button)
return;
button.ActionPressed -= ActionPressed;
button.ActionUnpressed -= ActionUnpressed;
button.ActionFocusExited -= ActionFocusExited;
}
public bool TryGetButtonIndex(ActionButton button, out int position)
{
if (button.Parent != this)