Actions Rework (#6791)

* Rejig Actions

* fix merge errors

* lambda-b-gon

* fix PAI, add innate actions

* Revert "fix PAI, add innate actions"

This reverts commit 4b501ac083e979e31ebd98d7b98077e0dbdd344b.

* Just fix by making nullable.

if only require: true actually did something somehow.

* Make AddActions() ensure an actions component

and misc comments

* misc cleanup

* Limit range even when not checking for obstructions

* remove old guardian code

* rename function and make EntityUid nullable

* fix magboot bug

* fix action search menu

* make targeting toggle all equivalent actions

* fix combat popups (enabling <-> disabling)
This commit is contained in:
Leon Friedrich
2022-02-25 17:12:29 +13:00
committed by GitHub
parent b99b1f4008
commit 5ac5dd6a64
135 changed files with 3122 additions and 5168 deletions

View File

@@ -1,23 +1,18 @@
using System.Collections.Generic;
using Content.Client.Actions.Assignments;
using Content.Client.DragDrop;
using Content.Client.HUD;
using Content.Client.Resources;
using Content.Client.Stylesheets;
using Content.Shared.Actions;
using Content.Shared.Actions.Prototypes;
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.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Actions.UI
@@ -27,13 +22,16 @@ namespace Content.Client.Actions.UI
/// </summary>
public sealed class ActionsUI : Container
{
private const float DragDeadZone = 10f;
private const float CustomTooltipDelay = 0.4f;
private readonly ClientActionsComponent _actionsComponent;
private readonly ActionManager _actionManager;
private readonly IEntityManager _entityManager;
private readonly IGameTiming _gameTiming;
internal readonly ActionsSystem System;
private readonly IGameHud _gameHud;
/// <summary>
/// The action component of the currently attached entity.
/// </summary>
public readonly ActionsComponent Component;
private readonly ActionSlot[] _slots;
private readonly GridContainer _slotContainer;
@@ -75,15 +73,13 @@ namespace Content.Client.Actions.UI
/// </summary>
public IEnumerable<ActionSlot> Slots => _slots;
public ActionsUI(ClientActionsComponent actionsComponent)
public ActionsUI(ActionsSystem system, ActionsComponent component)
{
SetValue(LayoutContainer.DebugProperty, true);
_actionsComponent = actionsComponent;
_actionManager = IoCManager.Resolve<ActionManager>();
_entityManager = IoCManager.Resolve<IEntityManager>();
_gameTiming = IoCManager.Resolve<IGameTiming>();
System = system;
Component = component;
_gameHud = IoCManager.Resolve<IGameHud>();
_menu = new ActionMenu(_actionsComponent, this);
_menu = new ActionMenu(this);
LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.End);
LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.Constrain);
@@ -196,7 +192,7 @@ namespace Content.Client.Actions.UI
_loadoutContainer.AddChild(nextHotbarIcon);
_loadoutContainer.AddChild(new Control { HorizontalExpand = true, SizeFlagsStretchRatio = 1 });
_slots = new ActionSlot[ClientActionsComponent.Slots];
_slots = new ActionSlot[ActionsSystem.Slots];
_dragShadow = new TextureRect
{
@@ -207,14 +203,14 @@ namespace Content.Client.Actions.UI
};
UserInterfaceManager.PopupRoot.AddChild(_dragShadow);
for (byte i = 0; i < ClientActionsComponent.Slots; i++)
for (byte i = 0; i < ActionsSystem.Slots; i++)
{
var slot = new ActionSlot(this, _menu, actionsComponent, i);
var slot = new ActionSlot(this, _menu, i);
_slotContainer.AddChild(slot);
_slots[i] = slot;
}
DragDropHelper = new DragDropHelper<ActionSlot>(OnBeginActionDrag, OnContinueActionDrag, OnEndActionDrag);
DragDropHelper = new DragDropHelper<ActionSlot>(OnBeginActionDrag, OnContinueActionDrag, OnEndActionDrag, DragDeadZone);
MinSize = (10, 400);
}
@@ -285,157 +281,47 @@ namespace Content.Client.Actions.UI
foreach (var actionSlot in Slots)
{
var assignedActionType = _actionsComponent.Assignments[SelectedHotbar, actionSlot.SlotIndex];
if (!assignedActionType.HasValue)
var action = System.Assignments[SelectedHotbar, actionSlot.SlotIndex];
if (action == null)
{
if (SelectingTargetFor == actionSlot)
StopTargeting(true);
actionSlot.Clear();
continue;
}
if (assignedActionType.Value.TryGetAction(out var actionType))
{
UpdateActionSlot(actionType, actionSlot, assignedActionType);
}
else if (assignedActionType.Value.TryGetItemActionWithoutItem(out var itemlessActionType))
{
UpdateActionSlot(itemlessActionType, actionSlot, assignedActionType);
}
else if (assignedActionType.Value.TryGetItemActionWithItem(out var itemActionType, out var item))
{
UpdateActionSlot(item, itemActionType, actionSlot, assignedActionType);
}
else
{
Logger.ErrorS("action", "unexpected Assignment type {0}",
assignedActionType.Value.Assignment);
actionSlot.Clear();
}
// This shouldn't happen, but could possible occur if theres some bug. Really the fact that the
// assignments aren't stored directly on the components or actions is just bad design and needs fixing.
DebugTools.Assert(Component.Actions.TryGetValue(action, out var compAction) && compAction == action);
UpdateActionSlot(action, actionSlot);
}
}
private void UpdateActionSlot(ActionType actionType, ActionSlot actionSlot, ActionAssignment? assignedActionType)
private void UpdateActionSlot(ActionType action, ActionSlot actionSlot)
{
if (_actionManager.TryGet(actionType, out var action))
{
actionSlot.Assign(action, true);
}
else
{
Logger.ErrorS("action", "unrecognized actionType {0}", assignedActionType);
actionSlot.Clear();
return;
}
actionSlot.Assign(action);
if (!_actionsComponent.TryGetActionState(actionType, out var actionState) || !actionState.Enabled)
if (!action.Enabled)
{
// action is currently disabled
// just revoked an action we were trying to target with, stop targeting
if (SelectingTargetFor?.Action != null && SelectingTargetFor.Action == action)
{
StopTargeting();
}
actionSlot.DisableAction();
actionSlot.Cooldown = null;
actionSlot.Disable();
}
else
{
// action is currently granted
actionSlot.EnableAction();
actionSlot.Cooldown = actionState.Cooldown;
// if we are targeting for this action and it's now on cooldown, stop targeting if we're supposed to
if (SelectingTargetFor?.Action != null && SelectingTargetFor.Action == action &&
actionState.IsOnCooldown(_gameTiming) && action.DeselectOnCooldown)
{
StopTargeting();
}
actionSlot.Enable();
}
// check if we need to toggle it
if (action.BehaviorType == BehaviorType.Toggle)
{
actionSlot.ToggledOn = actionState.ToggledOn;
}
actionSlot.UpdateIcons();
actionSlot.DrawModeChanged();
}
private void UpdateActionSlot(ItemActionType itemlessActionType, ActionSlot actionSlot,
ActionAssignment? assignedActionType)
{
if (_actionManager.TryGet(itemlessActionType, out var action))
{
actionSlot.Assign(action);
}
else
{
Logger.ErrorS("action", "unrecognized actionType {0}", assignedActionType);
actionSlot.Clear();
}
actionSlot.Cooldown = null;
}
private void UpdateActionSlot(EntityUid item, ItemActionType itemActionType, ActionSlot actionSlot,
ActionAssignment? assignedActionType)
{
if (!_entityManager.EntityExists(item)) return;
if (_actionManager.TryGet(itemActionType, out var action))
{
actionSlot.Assign(action, item, true);
}
else
{
Logger.ErrorS("action", "unrecognized actionType {0}", assignedActionType);
actionSlot.Clear();
return;
}
if (!_actionsComponent.TryGetItemActionState(itemActionType, item, out var actionState))
{
// action is no longer tied to an item, this should never happen as we
// check this at the start of this method. But just to be safe
// we will restore our assignment here to the correct state
Logger.ErrorS("action", "coding error, expected actionType {0} to have" +
" a state but it didn't", assignedActionType);
_actionsComponent.Assignments.AssignSlot(SelectedHotbar, actionSlot.SlotIndex,
ActionAssignment.For(itemActionType));
actionSlot.Assign(action);
return;
}
if (!actionState.Enabled)
{
// just disabled an action we were trying to target with, stop targeting
if (SelectingTargetFor?.Action != null && SelectingTargetFor.Action == action)
{
StopTargeting();
}
actionSlot.DisableAction();
}
else
{
// action is currently granted
actionSlot.EnableAction();
// if we are targeting with an action now on cooldown, stop targeting if we should
if (SelectingTargetFor?.Action != null && SelectingTargetFor.Action == action &&
SelectingTargetFor.Item == item &&
actionState.IsOnCooldown(_gameTiming) && action.DeselectOnCooldown)
{
StopTargeting();
}
}
actionSlot.Cooldown = actionState.Cooldown;
// check if we need to toggle it
if (action.BehaviorType == BehaviorType.Toggle)
{
actionSlot.ToggledOn = actionState.ToggledOn;
}
}
private void OnHotbarPaginate(GUIBoundKeyEventArgs args)
{
// rather than clicking the arrows themselves, the user can click the hbox so it's more
@@ -445,11 +331,11 @@ namespace Content.Client.Actions.UI
var rightness = args.RelativePosition.X / _loadoutContainer.Width;
if (rightness > 0.5)
{
ChangeHotbar((byte) ((SelectedHotbar + 1) % ClientActionsComponent.Hotbars));
ChangeHotbar((byte) ((SelectedHotbar + 1) % ActionsSystem.Hotbars));
}
else
{
var newBar = SelectedHotbar == 0 ? ClientActionsComponent.Hotbars - 1 : SelectedHotbar - 1;
var newBar = SelectedHotbar == 0 ? ActionsSystem.Hotbars - 1 : SelectedHotbar - 1;
ChangeHotbar((byte) newBar);
}
}
@@ -483,29 +369,35 @@ namespace Content.Client.Actions.UI
/// </summary>
private void StartTargeting(ActionSlot actionSlot)
{
if (actionSlot.Action == null)
return;
// If we were targeting something else we should stop
StopTargeting();
SelectingTargetFor = actionSlot;
// show it as toggled on to indicate we are currently selecting a target for it
if (!actionSlot.ToggledOn)
{
actionSlot.ToggledOn = true;
}
if (actionSlot.Action is TargetedAction targetAction)
System.StartTargeting(targetAction);
UpdateUI();
}
/// <summary>
/// Switch out of targeting mode if currently selecting target for an action
/// </summary>
public void StopTargeting()
public void StopTargeting(bool updating = false)
{
if (SelectingTargetFor == null) return;
if (SelectingTargetFor.ToggledOn)
{
SelectingTargetFor.ToggledOn = 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)
@@ -513,7 +405,6 @@ namespace Content.Client.Actions.UI
ToggleActionsMenu();
}
private void OnToggleActionsMenuTopButton(bool open)
{
if (open == _menu.IsOpen) return;
@@ -543,7 +434,7 @@ namespace Content.Client.Actions.UI
// 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();
_dragShadow.Texture = DragDropHelper.Dragged.Action.Icon?.Frame0();
LayoutContainer.SetPosition(_dragShadow, UserInterfaceManager.MousePositionScaled.Position - (32, 32));
DragDropHelper.Dragged.CancelPress();
return true;
@@ -574,6 +465,7 @@ namespace Content.Client.Actions.UI
{
var actionSlot = _slots[slot];
actionSlot.Depress(args.State == BoundKeyState.Down);
actionSlot.DrawModeChanged();
}
/// <summary>