Misc action fixes (#12046)

Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
This commit is contained in:
Leon Friedrich
2022-10-20 01:02:38 +13:00
committed by GitHub
parent 77fa2d50f1
commit f12d4a13d6
8 changed files with 158 additions and 38 deletions

View File

@@ -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)

View File

@@ -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()

View File

@@ -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))

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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!