Misc action fixes (#12046)
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
This commit is contained in:
@@ -180,24 +180,18 @@ namespace Content.Client.Actions
|
|||||||
|
|
||||||
private void OnPlayerAttached(EntityUid uid, ActionsComponent component, PlayerAttachedEvent args)
|
private void OnPlayerAttached(EntityUid uid, ActionsComponent component, PlayerAttachedEvent args)
|
||||||
{
|
{
|
||||||
if (uid != _playerManager.LocalPlayer?.ControlledEntity)
|
|
||||||
return;
|
|
||||||
|
|
||||||
LinkAllActions(component);
|
LinkAllActions(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlayerDetached(EntityUid uid, ActionsComponent component, PlayerDetachedEvent? args = null)
|
private void OnPlayerDetached(EntityUid uid, ActionsComponent component, PlayerDetachedEvent? args = null)
|
||||||
{
|
{
|
||||||
if (uid != _playerManager.LocalPlayer?.ControlledEntity)
|
|
||||||
return;
|
|
||||||
|
|
||||||
UnlinkAllActions();
|
UnlinkAllActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnlinkAllActions()
|
public void UnlinkAllActions()
|
||||||
{
|
{
|
||||||
UnlinkActions?.Invoke();
|
|
||||||
PlayerActions = null;
|
PlayerActions = null;
|
||||||
|
UnlinkActions?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LinkAllActions(ActionsComponent? actions = null)
|
public void LinkAllActions(ActionsComponent? actions = null)
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ public sealed class TargetOutlineSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
_shaderTargetValid = _prototypeManager.Index<ShaderPrototype>(ShaderTargetValid).Instance();
|
_shaderTargetValid = _prototypeManager.Index<ShaderPrototype>(ShaderTargetValid).InstanceUnique();
|
||||||
_shaderTargetInvalid = _prototypeManager.Index<ShaderPrototype>(ShaderTargetInvalid).Instance();
|
_shaderTargetInvalid = _prototypeManager.Index<ShaderPrototype>(ShaderTargetInvalid).InstanceUnique();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Disable()
|
public void Disable()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Content.Client.Actions;
|
using Content.Client.Actions;
|
||||||
|
using Content.Client.Construction;
|
||||||
using Content.Client.DragDrop;
|
using Content.Client.DragDrop;
|
||||||
using Content.Client.Gameplay;
|
using Content.Client.Gameplay;
|
||||||
using Content.Client.Hands;
|
using Content.Client.Hands;
|
||||||
@@ -14,6 +15,7 @@ using Content.Shared.Actions.ActionTypes;
|
|||||||
using Content.Shared.Input;
|
using Content.Shared.Input;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.Player;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controllers;
|
using Robust.Client.UserInterface.Controllers;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
@@ -38,6 +40,8 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entities = default!;
|
[Dependency] private readonly IEntityManager _entities = default!;
|
||||||
[Dependency] private readonly IOverlayManager _overlays = default!;
|
[Dependency] private readonly IOverlayManager _overlays = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
|
||||||
[UISystemDependency] private readonly ActionsSystem? _actionsSystem = default;
|
[UISystemDependency] private readonly ActionsSystem? _actionsSystem = default;
|
||||||
[UISystemDependency] private readonly InteractionOutlineSystem? _interactionOutline = default;
|
[UISystemDependency] private readonly InteractionOutlineSystem? _interactionOutline = default;
|
||||||
@@ -60,7 +64,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Action slot we are currently selecting a target for.
|
/// Action slot we are currently selecting a target for.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ActionButton? SelectingTargetFor { get; private set; }
|
public TargetedAction? SelectingTargetFor { get; private set; } = null;
|
||||||
|
|
||||||
public ActionUIController()
|
public ActionUIController()
|
||||||
{
|
{
|
||||||
@@ -139,9 +143,130 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
|||||||
builder
|
builder
|
||||||
.Bind(ContentKeyFunctions.OpenActionsMenu,
|
.Bind(ContentKeyFunctions.OpenActionsMenu,
|
||||||
InputCmdHandler.FromDelegate(_ => ToggleWindow()))
|
InputCmdHandler.FromDelegate(_ => ToggleWindow()))
|
||||||
|
.BindBefore(EngineKeyFunctions.Use, new PointerInputCmdHandler(TargetingOnUse, outsidePrediction: true),
|
||||||
|
typeof(ConstructionSystem), typeof(DragDropSystem))
|
||||||
|
.BindBefore(EngineKeyFunctions.UIRightClick, new PointerInputCmdHandler(TargetingCancel, outsidePrediction: true))
|
||||||
.Register<ActionUIController>();
|
.Register<ActionUIController>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool TargetingCancel(in PointerInputCmdArgs args)
|
||||||
|
{
|
||||||
|
if (!_timing.IsFirstTimePredicted)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// only do something for actual target-based actions
|
||||||
|
if (SelectingTargetFor == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
StopTargeting();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the user clicked somewhere, and they are currently targeting an action, try and perform it.
|
||||||
|
/// </summary>
|
||||||
|
private bool TargetingOnUse(in PointerInputCmdArgs args)
|
||||||
|
{
|
||||||
|
if (!_timing.IsFirstTimePredicted || _actionsSystem == null || SelectingTargetFor is not { } action)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_playerManager.LocalPlayer?.ControlledEntity is not EntityUid user)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!_entities.TryGetComponent(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 > _timing.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 TryTargetEntity(args, entTarget, user, comp) || !action.InteractOnMiss;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Logger.Error($"Unknown targeting action: {action.GetType()}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryTargetWorld(in PointerInputCmdArgs args, WorldTargetAction action, EntityUid user, ActionsComponent actionComp)
|
||||||
|
{
|
||||||
|
if (_actionsSystem == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var coords = args.Coordinates.ToMap(_entities);
|
||||||
|
|
||||||
|
if (!_actionsSystem.ValidateWorldTarget(user, coords, action))
|
||||||
|
{
|
||||||
|
// Invalid target.
|
||||||
|
if (action.DeselectOnMiss)
|
||||||
|
StopTargeting();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.ClientExclusive)
|
||||||
|
{
|
||||||
|
if (action.Event != null)
|
||||||
|
{
|
||||||
|
action.Event.Target = coords;
|
||||||
|
action.Event.Performer = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
_actionsSystem.PerformAction(actionComp, action, action.Event, _timing.CurTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_entities.RaisePredictiveEvent(new RequestPerformActionEvent(action, coords));
|
||||||
|
|
||||||
|
if (!action.Repeat)
|
||||||
|
StopTargeting();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryTargetEntity(in PointerInputCmdArgs args, EntityTargetAction action, EntityUid user, ActionsComponent actionComp)
|
||||||
|
{
|
||||||
|
if (_actionsSystem == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!_actionsSystem.ValidateEntityTarget(user, args.EntityUid, action))
|
||||||
|
{
|
||||||
|
if (action.DeselectOnMiss)
|
||||||
|
StopTargeting();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.ClientExclusive)
|
||||||
|
{
|
||||||
|
if (action.Event != null)
|
||||||
|
{
|
||||||
|
action.Event.Target = args.EntityUid;
|
||||||
|
action.Event.Performer = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
_actionsSystem.PerformAction(actionComp, action, action.Event, _timing.CurTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_entities.RaisePredictiveEvent(new RequestPerformActionEvent(action, args.EntityUid));
|
||||||
|
|
||||||
|
if (!action.Repeat)
|
||||||
|
StopTargeting();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public void UnloadButton()
|
public void UnloadButton()
|
||||||
{
|
{
|
||||||
if (ActionButton == null)
|
if (ActionButton == null)
|
||||||
@@ -204,7 +329,10 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
|||||||
if (CurrentPage[index] is not { } type)
|
if (CurrentPage[index] is not { } type)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_actionsSystem?.TriggerAction(type);
|
if (type is TargetedAction action)
|
||||||
|
ToggleTargeting(action);
|
||||||
|
else
|
||||||
|
_actionsSystem?.TriggerAction(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ChangePage(int index)
|
private void ChangePage(int index)
|
||||||
@@ -382,7 +510,8 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
|||||||
|
|
||||||
private void ClearList()
|
private void ClearList()
|
||||||
{
|
{
|
||||||
_window?.ResultsGrid.RemoveAllChildren();
|
if (_window?.Disposed == false)
|
||||||
|
_window.ResultsGrid.RemoveAllChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PopulateActions(IEnumerable<ActionType> actions)
|
private void PopulateActions(IEnumerable<ActionType> actions)
|
||||||
@@ -405,7 +534,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SearchAndDisplay()
|
private void SearchAndDisplay(ActionsComponent? component = null)
|
||||||
{
|
{
|
||||||
if (_window == null)
|
if (_window == null)
|
||||||
return;
|
return;
|
||||||
@@ -413,7 +542,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
|||||||
var search = _window.SearchBar.Text;
|
var search = _window.SearchBar.Text;
|
||||||
var filters = _window.FilterButton.SelectedKeys;
|
var filters = _window.FilterButton.SelectedKeys;
|
||||||
|
|
||||||
IEnumerable<ActionType>? actions = _actionsSystem?.PlayerActions?.Actions;
|
IEnumerable<ActionType>? actions = (component ?? _actionsSystem?.PlayerActions)?.Actions;
|
||||||
actions ??= Array.Empty<ActionType>();
|
actions ??= Array.Empty<ActionType>();
|
||||||
|
|
||||||
if (filters.Count == 0 && string.IsNullOrWhiteSpace(search))
|
if (filters.Count == 0 && string.IsNullOrWhiteSpace(search))
|
||||||
@@ -553,19 +682,20 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
|||||||
|
|
||||||
if (UIManager.CurrentlyHovered == button)
|
if (UIManager.CurrentlyHovered == button)
|
||||||
{
|
{
|
||||||
if (button.Action is not InstantAction)
|
_menuDragHelper.EndDrag();
|
||||||
|
|
||||||
|
if (button.Action is TargetedAction action)
|
||||||
{
|
{
|
||||||
// for target actions, we go into "select target" mode, we don't
|
// for target actions, we go into "select target" mode, we don't
|
||||||
// message the server until we actually pick our target.
|
// 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
|
// if we're clicking the same thing we're already targeting for, then we simply cancel
|
||||||
// targeting
|
// targeting
|
||||||
ToggleTargeting(button);
|
ToggleTargeting(action);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_actionsSystem?.TriggerAction(button.Action);
|
_actionsSystem?.TriggerAction(button.Action);
|
||||||
_menuDragHelper.EndDrag();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -704,12 +834,14 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
|||||||
{
|
{
|
||||||
LoadDefaultActions(component);
|
LoadDefaultActions(component);
|
||||||
_container?.SetActionData(_pages[DefaultPageIndex]);
|
_container?.SetActionData(_pages[DefaultPageIndex]);
|
||||||
|
SearchAndDisplay(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnComponentUnlinked()
|
private void OnComponentUnlinked()
|
||||||
{
|
{
|
||||||
_container?.ClearActionData();
|
_container?.ClearActionData();
|
||||||
//TODO: Clear button data
|
SearchAndDisplay();
|
||||||
|
StopTargeting();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadDefaultActions(ActionsComponent component)
|
private void LoadDefaultActions(ActionsComponent component)
|
||||||
@@ -755,32 +887,26 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
|||||||
/// targeting with the specified slot.
|
/// targeting with the specified slot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="slot"></param>
|
/// <param name="slot"></param>
|
||||||
public void ToggleTargeting(ActionButton slot)
|
public void ToggleTargeting(TargetedAction action)
|
||||||
{
|
{
|
||||||
if (SelectingTargetFor == slot)
|
if (SelectingTargetFor == action)
|
||||||
{
|
{
|
||||||
StopTargeting();
|
StopTargeting();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StartTargeting(slot);
|
StartTargeting(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Puts us in targeting mode, where we need to pick either a target point or entity
|
/// Puts us in targeting mode, where we need to pick either a target point or entity
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void StartTargeting(ActionButton actionSlot)
|
private void StartTargeting(TargetedAction action)
|
||||||
{
|
{
|
||||||
if (actionSlot.Action == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// If we were targeting something else we should stop
|
// If we were targeting something else we should stop
|
||||||
StopTargeting();
|
StopTargeting();
|
||||||
|
|
||||||
SelectingTargetFor = actionSlot;
|
SelectingTargetFor = action;
|
||||||
|
|
||||||
if (actionSlot.Action is not TargetedAction action)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// override "held-item" overlay
|
// override "held-item" overlay
|
||||||
if (action.TargetingIndicator && _overlays.TryGetOverlay<ShowHandItemOverlay>(out var handOverlay))
|
if (action.TargetingIndicator && _overlays.TryGetOverlay<ShowHandItemOverlay>(out var handOverlay))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Client.Actions.UI;
|
using Content.Client.Actions.UI;
|
||||||
using Content.Client.Cooldown;
|
using Content.Client.Cooldown;
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
using Content.Shared.Actions;
|
using Content.Shared.Actions;
|
||||||
@@ -274,7 +274,7 @@ public sealed class ActionButton : Control
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((Controller.SelectingTargetFor?.Action == Action || Action.Toggled) && Action.IconOn != null)
|
if ((Controller.SelectingTargetFor == Action || Action.Toggled) && Action.IconOn != null)
|
||||||
SetActionIcon(Action.IconOn.Frame0());
|
SetActionIcon(Action.IconOn.Frame0());
|
||||||
else
|
else
|
||||||
SetActionIcon(Action.Icon?.Frame0());
|
SetActionIcon(Action.Icon?.Frame0());
|
||||||
@@ -391,7 +391,7 @@ public sealed class ActionButton : Control
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if it's toggled on, always show the toggled on style (currently same as depressed style)
|
// if it's toggled on, always show the toggled on style (currently same as depressed style)
|
||||||
if (Action.Toggled || Controller.SelectingTargetFor == this)
|
if (Action.Toggled || Controller.SelectingTargetFor == Action)
|
||||||
{
|
{
|
||||||
// when there's a toggle sprite, we're showing that sprite instead of highlighting this slot
|
// when there's a toggle sprite, we're showing that sprite instead of highlighting this slot
|
||||||
SetOnlyStylePseudoClass(Action.IconOn != null
|
SetOnlyStylePseudoClass(Action.IconOn != null
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Shared.Actions.ActionTypes;
|
using Content.Shared.Actions.ActionTypes;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
|
||||||
@@ -7,8 +7,6 @@ namespace Content.Client.UserInterface.Systems.Actions.Controls;
|
|||||||
[Virtual]
|
[Virtual]
|
||||||
public class ActionButtonContainer : GridContainer
|
public class ActionButtonContainer : GridContainer
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
|
||||||
|
|
||||||
public event Action<GUIBoundKeyEventArgs, ActionButton>? ActionPressed;
|
public event Action<GUIBoundKeyEventArgs, ActionButton>? ActionPressed;
|
||||||
public event Action<GUIBoundKeyEventArgs, ActionButton>? ActionUnpressed;
|
public event Action<GUIBoundKeyEventArgs, ActionButton>? ActionUnpressed;
|
||||||
public event Action<ActionButton>? ActionFocusExited;
|
public event Action<ActionButton>? ActionFocusExited;
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO using targeted actions while combat mode is enabled should NOT trigger attacks.
|
||||||
|
|
||||||
var useDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use);
|
var useDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use);
|
||||||
var altDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.UseSecondary);
|
var altDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.UseSecondary);
|
||||||
var currentTime = Timing.CurTime;
|
var currentTime = Timing.CurTime;
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
|||||||
return _interactionSystem.InRangeUnobstructed(user, coords, range: action.Range);
|
return _interactionSystem.InRangeUnobstructed(user, coords, range: action.Range);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void PerformAction(ActionsComponent component, ActionType action, BaseActionEvent? actionEvent, TimeSpan curTime)
|
public void PerformAction(ActionsComponent component, ActionType action, BaseActionEvent? actionEvent, TimeSpan curTime)
|
||||||
{
|
{
|
||||||
var handled = false;
|
var handled = false;
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ dragon-spawn-action-popup-message-fail-no-eggs = You don't have the stamina to d
|
|||||||
action-name-devour = [color=red]Devour[/color]
|
action-name-devour = [color=red]Devour[/color]
|
||||||
action-description-devour = Attempt to break a structure with your jaws or swallow a creature.
|
action-description-devour = Attempt to break a structure with your jaws or swallow a creature.
|
||||||
|
|
||||||
action-name-carp-summon = Summon carp
|
action-name-carp-rift = Summon Carp Rift
|
||||||
action-description-carp-summon = Summon a carp to aid you at seizing the station!
|
action-description-carp-rift = Summons a carp rift that will periodically spawns carps.
|
||||||
|
|
||||||
# Rifts
|
# Rifts
|
||||||
carp-rift-warning = A rift is causing an unnaturally large energy flux at {$location}. Stop it at all costs!
|
carp-rift-warning = A rift is causing an unnaturally large energy flux at {$location}. Stop it at all costs!
|
||||||
|
|||||||
Reference in New Issue
Block a user