Refactor actions to be entities with components (#19900)
This commit is contained in:
@@ -13,7 +13,6 @@ using Content.Client.UserInterface.Systems.Actions.Widgets;
|
||||
using Content.Client.UserInterface.Systems.Actions.Windows;
|
||||
using Content.Client.UserInterface.Systems.Gameplay;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -65,7 +64,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
/// <summary>
|
||||
/// Action slot we are currently selecting a target for.
|
||||
/// </summary>
|
||||
public TargetedAction? SelectingTargetFor { get; private set; }
|
||||
public EntityUid? SelectingTargetFor { get; private set; }
|
||||
|
||||
public ActionUIController()
|
||||
{
|
||||
@@ -175,7 +174,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
/// </summary>
|
||||
private bool TargetingOnUse(in PointerInputCmdArgs args)
|
||||
{
|
||||
if (!_timing.IsFirstTimePredicted || _actionsSystem == null || SelectingTargetFor is not { } action)
|
||||
if (!_timing.IsFirstTimePredicted || _actionsSystem == null || SelectingTargetFor is not { } actionId)
|
||||
return false;
|
||||
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity is not { } user)
|
||||
@@ -184,6 +183,12 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
if (!EntityManager.TryGetComponent(user, out ActionsComponent? comp))
|
||||
return false;
|
||||
|
||||
if (!_actionsSystem.TryGetActionData(actionId, out var baseAction) ||
|
||||
baseAction is not BaseTargetActionComponent action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is the action currently valid?
|
||||
if (!action.Enabled
|
||||
|| action.Charges is 0
|
||||
@@ -196,19 +201,19 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case WorldTargetAction mapTarget:
|
||||
return TryTargetWorld(args, mapTarget, user, comp) || !action.InteractOnMiss;
|
||||
case WorldTargetActionComponent mapTarget:
|
||||
return TryTargetWorld(args, actionId, mapTarget, user, comp) || !mapTarget.InteractOnMiss;
|
||||
|
||||
case EntityTargetAction entTarget:
|
||||
return TryTargetEntity(args, entTarget, user, comp) || !action.InteractOnMiss;
|
||||
case EntityTargetActionComponent entTarget:
|
||||
return TryTargetEntity(args, actionId, entTarget, user, comp) || !entTarget.InteractOnMiss;
|
||||
|
||||
default:
|
||||
Logger.Error($"Unknown targeting action: {action.GetType()}");
|
||||
Logger.Error($"Unknown targeting action: {actionId.GetType()}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryTargetWorld(in PointerInputCmdArgs args, WorldTargetAction action, EntityUid user, ActionsComponent actionComp)
|
||||
private bool TryTargetWorld(in PointerInputCmdArgs args, EntityUid actionId, WorldTargetActionComponent action, EntityUid user, ActionsComponent actionComp)
|
||||
{
|
||||
if (_actionsSystem == null)
|
||||
return false;
|
||||
@@ -232,10 +237,10 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
action.Event.Performer = user;
|
||||
}
|
||||
|
||||
_actionsSystem.PerformAction(user, actionComp, action, action.Event, _timing.CurTime);
|
||||
_actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
|
||||
}
|
||||
else
|
||||
EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(action, coords));
|
||||
EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(actionId, coords));
|
||||
|
||||
if (!action.Repeat)
|
||||
StopTargeting();
|
||||
@@ -243,7 +248,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryTargetEntity(in PointerInputCmdArgs args, EntityTargetAction action, EntityUid user, ActionsComponent actionComp)
|
||||
private bool TryTargetEntity(in PointerInputCmdArgs args, EntityUid actionId, EntityTargetActionComponent action, EntityUid user, ActionsComponent actionComp)
|
||||
{
|
||||
if (_actionsSystem == null)
|
||||
return false;
|
||||
@@ -264,10 +269,10 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
action.Event.Performer = user;
|
||||
}
|
||||
|
||||
_actionsSystem.PerformAction(user, actionComp, action, action.Event, _timing.CurTime);
|
||||
_actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
|
||||
}
|
||||
else
|
||||
EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(action, args.EntityUid));
|
||||
EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(actionId, args.EntityUid));
|
||||
|
||||
if (!action.Repeat)
|
||||
StopTargeting();
|
||||
@@ -322,13 +327,17 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private void TriggerAction(int index)
|
||||
{
|
||||
if (CurrentPage[index] is not { } type)
|
||||
if (_actionsSystem == null ||
|
||||
CurrentPage[index] is not { } actionId ||
|
||||
!_actionsSystem.TryGetActionData(actionId, out var baseAction))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (type is TargetedAction action)
|
||||
ToggleTargeting(action);
|
||||
if (baseAction is BaseTargetActionComponent action)
|
||||
ToggleTargeting(actionId, action);
|
||||
else
|
||||
_actionsSystem?.TriggerAction(type);
|
||||
_actionsSystem?.TriggerAction(actionId, baseAction);
|
||||
}
|
||||
|
||||
private void ChangePage(int index)
|
||||
@@ -360,14 +369,14 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
ChangePage(_currentPageIndex + 1);
|
||||
}
|
||||
|
||||
private void AppendAction(ActionType action)
|
||||
private void AppendAction(EntityUid action)
|
||||
{
|
||||
if (_container == null)
|
||||
return;
|
||||
|
||||
foreach (var button in _container.GetButtons())
|
||||
{
|
||||
if (button.Action != null)
|
||||
if (button.ActionId != null)
|
||||
continue;
|
||||
|
||||
SetAction(button, action);
|
||||
@@ -388,39 +397,45 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
}
|
||||
}
|
||||
|
||||
private void OnActionAdded(ActionType action)
|
||||
private void OnActionAdded(EntityUid actionId)
|
||||
{
|
||||
if (_actionsSystem == null ||
|
||||
!_actionsSystem.TryGetActionData(actionId, out var action))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// if the action is toggled when we add it, start targeting
|
||||
if (action is TargetedAction targetAction && action.Toggled)
|
||||
StartTargeting(targetAction);
|
||||
if (action is BaseTargetActionComponent targetAction && action.Toggled)
|
||||
StartTargeting(actionId, targetAction);
|
||||
|
||||
foreach (var page in _pages)
|
||||
{
|
||||
for (var i = 0; i < page.Size; i++)
|
||||
{
|
||||
if (page[i] == action)
|
||||
if (page[i] == actionId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppendAction(action);
|
||||
AppendAction(actionId);
|
||||
SearchAndDisplay();
|
||||
}
|
||||
|
||||
private void OnActionRemoved(ActionType action)
|
||||
private void OnActionRemoved(EntityUid actionId)
|
||||
{
|
||||
if (_container == null)
|
||||
return;
|
||||
|
||||
// stop targeting if the action is removed
|
||||
if (action == SelectingTargetFor)
|
||||
if (actionId == SelectingTargetFor)
|
||||
StopTargeting();
|
||||
|
||||
foreach (var button in _container.GetButtons())
|
||||
{
|
||||
if (button.Action == action)
|
||||
if (button.ActionId == actionId)
|
||||
{
|
||||
SetAction(button, null);
|
||||
}
|
||||
@@ -430,7 +445,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
for (var i = 0; i < page.Size; i++)
|
||||
{
|
||||
if (page[i] == action)
|
||||
if (page[i] == actionId)
|
||||
{
|
||||
page[i] = null;
|
||||
}
|
||||
@@ -440,15 +455,15 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
SearchAndDisplay();
|
||||
}
|
||||
|
||||
private void OnActionReplaced(ActionType existing, ActionType action)
|
||||
private void OnActionReplaced(EntityUid actionId)
|
||||
{
|
||||
if (_container == null)
|
||||
return;
|
||||
|
||||
foreach (var button in _container.GetButtons())
|
||||
{
|
||||
if (button.Action == existing)
|
||||
button.UpdateData(action);
|
||||
if (button.ActionId == actionId)
|
||||
button.UpdateData(actionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,15 +514,15 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
}
|
||||
}
|
||||
|
||||
private bool MatchesFilter(ActionType action, Filters filter)
|
||||
private bool MatchesFilter(BaseActionComponent action, Filters filter)
|
||||
{
|
||||
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.Instant => action is InstantAction,
|
||||
Filters.Targeted => action is TargetedAction,
|
||||
Filters.Instant => action is InstantActionComponent,
|
||||
Filters.Targeted => action is BaseTargetActionComponent,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(filter), filter, null)
|
||||
};
|
||||
}
|
||||
@@ -518,7 +533,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
_window.ResultsGrid.RemoveAllChildren();
|
||||
}
|
||||
|
||||
private void PopulateActions(IEnumerable<ActionType> actions)
|
||||
private void PopulateActions(IEnumerable<(EntityUid Id, BaseActionComponent Comp)> actions)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
@@ -529,7 +544,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
var button = new ActionButton {Locked = true};
|
||||
|
||||
button.UpdateData(action);
|
||||
button.UpdateData(action.Id);
|
||||
button.ActionPressed += OnWindowActionPressed;
|
||||
button.ActionUnpressed += OnWindowActionUnPressed;
|
||||
button.ActionFocusExited += OnWindowActionFocusExisted;
|
||||
@@ -538,16 +553,14 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchAndDisplay(ActionsComponent? component = null)
|
||||
private void SearchAndDisplay()
|
||||
{
|
||||
if (_window == null)
|
||||
if (_window is not { Disposed: false } || _actionsSystem == null)
|
||||
return;
|
||||
|
||||
var search = _window.SearchBar.Text;
|
||||
var filters = _window.FilterButton.SelectedKeys;
|
||||
|
||||
IEnumerable<ActionType>? actions = (component ?? _actionsSystem?.PlayerActions)?.Actions;
|
||||
actions ??= Array.Empty<ActionType>();
|
||||
var actions = _actionsSystem.GetClientActions();
|
||||
|
||||
if (filters.Count == 0 && string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
@@ -557,45 +570,46 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
actions = actions.Where(action =>
|
||||
{
|
||||
if (filters.Count > 0 && filters.Any(filter => !MatchesFilter(action, filter)))
|
||||
if (filters.Count > 0 && filters.Any(filter => !MatchesFilter(action.Comp, filter)))
|
||||
return false;
|
||||
|
||||
if (action.Keywords.Any(keyword => search.Contains(keyword, StringComparison.OrdinalIgnoreCase)))
|
||||
if (action.Comp.Keywords.Any(keyword => search.Contains(keyword, StringComparison.OrdinalIgnoreCase)))
|
||||
return true;
|
||||
|
||||
if (action.DisplayName.Contains(search, StringComparison.OrdinalIgnoreCase))
|
||||
var name = EntityManager.GetComponent<MetaDataComponent>(action.Id).EntityName;
|
||||
if (name.Contains(search, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
if (action.Provider == null || action.Provider == _playerManager.LocalPlayer?.ControlledEntity)
|
||||
if (action.Comp.Provider == null || action.Comp.Provider == _playerManager.LocalPlayer?.ControlledEntity)
|
||||
return false;
|
||||
|
||||
var name = EntityManager.GetComponent<MetaDataComponent>(action.Provider.Value).EntityName;
|
||||
return name.Contains(search, StringComparison.OrdinalIgnoreCase);
|
||||
var providerName = EntityManager.GetComponent<MetaDataComponent>(action.Comp.Provider.Value).EntityName;
|
||||
return providerName.Contains(search, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
|
||||
PopulateActions(actions);
|
||||
}
|
||||
|
||||
private void SetAction(ActionButton button, ActionType? type)
|
||||
private void SetAction(ActionButton button, EntityUid? actionId)
|
||||
{
|
||||
int position;
|
||||
|
||||
if (type == null)
|
||||
if (actionId == null)
|
||||
{
|
||||
button.ClearData();
|
||||
if (_container?.TryGetButtonIndex(button, out position) ?? false)
|
||||
{
|
||||
CurrentPage[position] = type;
|
||||
CurrentPage[position] = actionId;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (button.TryReplaceWith(type) &&
|
||||
if (button.TryReplaceWith(actionId.Value) &&
|
||||
_container != null &&
|
||||
_container.TryGetButtonIndex(button, out position))
|
||||
{
|
||||
CurrentPage[position] = type;
|
||||
CurrentPage[position] = actionId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,7 +617,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
if (UIManager.CurrentlyHovered is ActionButton button)
|
||||
{
|
||||
if (!_menuDragHelper.IsDragging || _menuDragHelper.Dragged?.Action is not { } type)
|
||||
if (!_menuDragHelper.IsDragging || _menuDragHelper.Dragged?.ActionId is not { } type)
|
||||
{
|
||||
_menuDragHelper.EndDrag();
|
||||
return;
|
||||
@@ -669,7 +683,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
if (args.Function == EngineKeyFunctions.UIClick)
|
||||
{
|
||||
if (button.Action == null)
|
||||
if (button.ActionId == null)
|
||||
{
|
||||
var ev = new FillActionSlotEvent();
|
||||
EntityManager.EventBus.RaiseEvent(EventSource.Local, ev);
|
||||
@@ -692,25 +706,28 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private void OnActionUnpressed(GUIBoundKeyEventArgs args, ActionButton button)
|
||||
{
|
||||
if (args.Function != EngineKeyFunctions.UIClick)
|
||||
if (args.Function != EngineKeyFunctions.UIClick || _actionsSystem == null)
|
||||
return;
|
||||
|
||||
if (UIManager.CurrentlyHovered == button)
|
||||
{
|
||||
_menuDragHelper.EndDrag();
|
||||
|
||||
if (button.Action is TargetedAction action)
|
||||
if (_actionsSystem.TryGetActionData(button.ActionId, out var baseAction))
|
||||
{
|
||||
// for target actions, we go into "select target" mode, we don't
|
||||
// message the server until we actually pick our target.
|
||||
if (baseAction is BaseTargetActionComponent action)
|
||||
{
|
||||
// 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
|
||||
ToggleTargeting(action);
|
||||
return;
|
||||
// if we're clicking the same thing we're already targeting for, then we simply cancel
|
||||
// targeting
|
||||
ToggleTargeting(button.ActionId.Value, action);
|
||||
return;
|
||||
}
|
||||
|
||||
_actionsSystem?.TriggerAction(button.ActionId.Value, baseAction);
|
||||
}
|
||||
|
||||
_actionsSystem?.TriggerAction(button.Action);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -722,7 +739,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private bool OnMenuBeginDrag()
|
||||
{
|
||||
if (_menuDragHelper.Dragged?.Action is { } action)
|
||||
if (_actionsSystem != null && _actionsSystem.TryGetActionData(_menuDragHelper.Dragged?.ActionId, out var action))
|
||||
{
|
||||
if (action.EntityIcon != null)
|
||||
{
|
||||
@@ -828,7 +845,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
foreach (ref var assignment in CollectionsMarshal.AsSpan(assignments))
|
||||
{
|
||||
_pages[assignment.Hotbar][assignment.Slot] = assignment.Action;
|
||||
_pages[assignment.Hotbar][assignment.Slot] = assignment.ActionId;
|
||||
}
|
||||
|
||||
_container?.SetActionData(_pages[_currentPageIndex]);
|
||||
@@ -864,7 +881,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
LoadDefaultActions(component);
|
||||
_container?.SetActionData(_pages[DefaultPageIndex]);
|
||||
SearchAndDisplay(component);
|
||||
SearchAndDisplay();
|
||||
}
|
||||
|
||||
private void OnComponentUnlinked()
|
||||
@@ -876,7 +893,10 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private void LoadDefaultActions(ActionsComponent component)
|
||||
{
|
||||
var actions = component.Actions.Where(actionType => actionType.AutoPopulate).ToList();
|
||||
if (_actionsSystem == null)
|
||||
return;
|
||||
|
||||
var actions = _actionsSystem.GetClientActions().Where(action => action.Comp.AutoPopulate).ToList();
|
||||
|
||||
var offset = 0;
|
||||
var totalPages = _pages.Count;
|
||||
@@ -892,7 +912,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
var actionIndex = slot + offset;
|
||||
if (actionIndex < actions.Count)
|
||||
{
|
||||
page[slot] = actions[slot + offset];
|
||||
page[slot] = actions[slot + offset].Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -916,26 +936,26 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
/// If currently targeting with no slot or a different slot, switches to
|
||||
/// targeting with the specified slot.
|
||||
/// </summary>
|
||||
private void ToggleTargeting(TargetedAction action)
|
||||
private void ToggleTargeting(EntityUid actionId, BaseTargetActionComponent action)
|
||||
{
|
||||
if (SelectingTargetFor == action)
|
||||
if (SelectingTargetFor == actionId)
|
||||
{
|
||||
StopTargeting();
|
||||
return;
|
||||
}
|
||||
|
||||
StartTargeting(action);
|
||||
StartTargeting(actionId, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts us in targeting mode, where we need to pick either a target point or entity
|
||||
/// </summary>
|
||||
private void StartTargeting(TargetedAction action)
|
||||
private void StartTargeting(EntityUid actionId, BaseTargetActionComponent action)
|
||||
{
|
||||
// If we were targeting something else we should stop
|
||||
StopTargeting();
|
||||
|
||||
SelectingTargetFor = action;
|
||||
SelectingTargetFor = actionId;
|
||||
|
||||
// override "held-item" overlay
|
||||
if (action.TargetingIndicator && _overlays.TryGetOverlay<ShowHandItemOverlay>(out var handOverlay))
|
||||
@@ -955,7 +975,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
// - Add a yes/no checkmark where the HandItemOverlay usually is
|
||||
|
||||
// Highlight valid entity targets
|
||||
if (action is not EntityTargetAction entityAction)
|
||||
if (action is not EntityTargetActionComponent entityAction)
|
||||
return;
|
||||
|
||||
Func<EntityUid, bool>? predicate = null;
|
||||
@@ -992,20 +1012,20 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
//TODO: Serialize this shit
|
||||
private sealed class ActionPage
|
||||
{
|
||||
private readonly ActionType?[] _data;
|
||||
private readonly EntityUid?[] _data;
|
||||
|
||||
public ActionPage(int size)
|
||||
{
|
||||
_data = new ActionType?[size];
|
||||
_data = new EntityUid?[size];
|
||||
}
|
||||
|
||||
public ActionType? this[int index]
|
||||
public EntityUid? this[int index]
|
||||
{
|
||||
get => _data[index];
|
||||
set => _data[index] = value;
|
||||
}
|
||||
|
||||
public static implicit operator ActionType?[](ActionPage p)
|
||||
public static implicit operator EntityUid?[](ActionPage p)
|
||||
{
|
||||
return p._data.ToArray();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user