Patched Actions Rework (#6899)

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

* fix networking

* Allow action locking

* prevent telepathy
This commit is contained in:
Leon Friedrich
2022-02-26 18:24:08 +13:00
committed by GitHub
parent d32f884157
commit ff7d4ed9f6
135 changed files with 3156 additions and 5166 deletions

View File

@@ -1,5 +1,7 @@
using System;
using Content.Client.Stylesheets;
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
@@ -89,7 +91,7 @@ namespace Content.Client.Actions.UI
{
var duration = Cooldown.Value.End - Cooldown.Value.Start;
_cooldownLabel.SetMessage(FormattedMessage.FromMarkup(
$"[color=#a10505]{duration.Seconds} sec cooldown ({timeLeft.Seconds + 1} sec remaining)[/color]"));
$"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]"));
_cooldownLabel.Visible = true;
}
else

View File

@@ -1,22 +1,16 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Content.Client.Actions.Assignments;
using Content.Client.DragDrop;
using Content.Client.HUD;
using Content.Client.Stylesheets;
using Content.Shared.Actions;
using Content.Shared.Actions.Prototypes;
using Content.Shared.Actions.ActionTypes;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.Utility;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Timing;
using static Robust.Client.UserInterface.Controls.BaseButton;
using static Robust.Client.UserInterface.Controls.BoxContainer;
@@ -29,28 +23,35 @@ namespace Content.Client.Actions.UI
/// </summary>
public sealed class ActionMenu : DefaultWindow
{
private const string ItemTag = "item";
private const string NotItemTag = "not item";
private const string InstantActionTag = "instant";
private const string ToggleActionTag = "toggle";
private const string TargetActionTag = "target";
private const string AllActionsTag = "all";
private const string GrantedActionsTag = "granted";
// Pre-defined global filters that can be used to select actions based on their properties (as opposed to their
// own yaml-defined filters).
// TODO LOC STRINGs
private const string AllFilter = "all";
private const string ItemFilter = "item";
private const string InnateFilter = "innate";
private const string EnabledFilter = "enabled";
private const string InstantFilter = "instant";
private const string TargetedFilter = "targeted";
private readonly string[] _filters =
{
AllFilter,
ItemFilter,
InnateFilter,
EnabledFilter,
InstantFilter,
TargetedFilter
};
private const int MinSearchLength = 3;
private static readonly Regex NonAlphanumeric = new Regex(@"\W", RegexOptions.Compiled);
private static readonly Regex Whitespace = new Regex(@"\s+", RegexOptions.Compiled);
private static readonly BaseActionPrototype[] EmptyActionList = Array.Empty<BaseActionPrototype>();
/// <summary>
/// Is an action currently being dragged from this window?
/// </summary>
public bool IsDragging => _dragDropHelper.IsDragging;
// parallel list of actions currently selectable in itemList
private BaseActionPrototype[] _actionList = new BaseActionPrototype[0];
private readonly ActionManager _actionManager;
private readonly ClientActionsComponent _actionsComponent;
private readonly ActionsUI _actionsUI;
private readonly LineEdit _searchBar;
private readonly MultiselectOptionButton<string> _filterButton;
@@ -60,17 +61,16 @@ namespace Content.Client.Actions.UI
private readonly TextureRect _dragShadow;
private readonly IGameHud _gameHud;
private readonly DragDropHelper<ActionMenuItem> _dragDropHelper;
private readonly IEntityManager _entMan;
public ActionMenu(ClientActionsComponent actionsComponent, ActionsUI actionsUI)
public ActionMenu(ActionsUI actionsUI)
{
_actionsComponent = actionsComponent;
_actionsUI = actionsUI;
_actionManager = IoCManager.Resolve<ActionManager>();
_gameHud = IoCManager.Resolve<IGameHud>();
_entMan = IoCManager.Resolve<IEntityManager>();
Title = Loc.GetString("ui-actionmenu-title");
MinSize = (300, 300);
MinSize = (320, 300);
Contents.AddChild(new BoxContainer
{
@@ -115,27 +115,14 @@ namespace Content.Client.Actions.UI
}
});
// populate filters from search tags
var filterTags = new List<string>();
foreach (var action in _actionManager.EnumerateActions())
{
filterTags.AddRange(action.Filters);
}
// special one to filter to only include item actions
filterTags.Add(ItemTag);
filterTags.Add(NotItemTag);
filterTags.Add(InstantActionTag);
filterTags.Add(ToggleActionTag);
filterTags.Add(TargetActionTag);
filterTags.Add(AllActionsTag);
filterTags.Add(GrantedActionsTag);
foreach (var tag in filterTags.Distinct().OrderBy(tag => tag))
foreach (var tag in _filters)
{
_filterButton.AddItem( CultureInfo.CurrentCulture.TextInfo.ToTitleCase(tag), tag);
}
// default to showing all actions.
_filterButton.SelectKey(AllFilter);
UpdateFilterLabel();
_dragShadow = new TextureRect
@@ -157,13 +144,6 @@ namespace Content.Client.Actions.UI
_searchBar.OnTextChanged += OnSearchTextChanged;
_filterButton.OnItemSelected += OnFilterItemSelected;
_gameHud.ActionsButtonDown = true;
foreach (var actionMenuControl in _resultsGrid.Children)
{
var actionMenuItem = (ActionMenuItem) actionMenuControl;
actionMenuItem.OnButtonDown += OnItemButtonDown;
actionMenuItem.OnButtonUp += OnItemButtonUp;
actionMenuItem.OnPressed += OnItemPressed;
}
}
protected override void ExitedTree()
@@ -199,7 +179,7 @@ namespace Content.Client.Actions.UI
private bool OnBeginActionDrag()
{
_dragShadow.Texture = _dragDropHelper.Dragged!.Action.Icon.Frame0();
_dragShadow.Texture = _dragDropHelper.Dragged?.Action?.Icon?.Frame0();
// don't make visible until frameupdate, otherwise it'll flicker
LayoutContainer.SetPosition(_dragShadow, UserInterfaceManager.MousePositionScaled.Position - (32, 32));
return true;
@@ -244,47 +224,7 @@ namespace Content.Client.Actions.UI
return;
}
// drag and drop
switch (_dragDropHelper.Dragged.Action)
{
// assign the dragged action to the target slot
case ActionPrototype actionPrototype:
_actionsComponent.Assignments.AssignSlot(_actionsUI.SelectedHotbar, targetSlot.SlotIndex, ActionAssignment.For(actionPrototype.ActionType));
break;
case ItemActionPrototype itemActionPrototype:
// the action menu doesn't show us if the action has an associated item,
// so when we perform the assignment, we should check if we currently have an unassigned state
// for this item and assign it tied to that item if so, otherwise assign it "itemless"
// this is not particularly efficient but we don't maintain an index from
// item action type to its action states, and this method should be pretty infrequent so it's probably fine
var assigned = false;
foreach (var (item, itemStates) in _actionsComponent.ItemActionStates())
{
foreach (var (actionType, _) in itemStates)
{
if (actionType != itemActionPrototype.ActionType) continue;
var assignment = ActionAssignment.For(actionType, item);
if (_actionsComponent.Assignments.HasAssignment(assignment)) continue;
// no assignment for this state, assign tied to the item
assigned = true;
_actionsComponent.Assignments.AssignSlot(_actionsUI.SelectedHotbar, targetSlot.SlotIndex, assignment);
break;
}
if (assigned)
{
break;
}
}
if (!assigned)
{
_actionsComponent.Assignments.AssignSlot(_actionsUI.SelectedHotbar, targetSlot.SlotIndex, ActionAssignment.For(itemActionPrototype.ActionType));
}
break;
}
_actionsUI.System.Assignments.AssignSlot(_actionsUI.SelectedHotbar, targetSlot.SlotIndex, _dragDropHelper.Dragged.Action);
_actionsUI.UpdateUI();
}
@@ -300,19 +240,8 @@ namespace Content.Client.Actions.UI
private void OnItemPressed(ButtonEventArgs args)
{
if (args.Button is not ActionMenuItem actionMenuItem) return;
switch (actionMenuItem.Action)
{
case ActionPrototype actionPrototype:
_actionsComponent.Assignments.AutoPopulate(ActionAssignment.For(actionPrototype.ActionType), _actionsUI.SelectedHotbar);
break;
case ItemActionPrototype itemActionPrototype:
_actionsComponent.Assignments.AutoPopulate(ActionAssignment.For(itemActionPrototype.ActionType), _actionsUI.SelectedHotbar);
break;
default:
Logger.ErrorS("action", "unexpected action prototype {0}", actionMenuItem.Action);
break;
}
_actionsUI.System.Assignments.AutoPopulate(actionMenuItem.Action, _actionsUI.SelectedHotbar);
_actionsUI.UpdateUI();
}
@@ -341,7 +270,7 @@ namespace Content.Client.Actions.UI
return;
}
var matchingActions = _actionManager.EnumerateActions()
var matchingActions = _actionsUI.Component.Actions
.Where(a => MatchesSearchCriteria(a, search, _filterButton.SelectedKeys));
PopulateActions(matchingActions);
@@ -361,7 +290,7 @@ namespace Content.Client.Actions.UI
}
}
private bool MatchesSearchCriteria(BaseActionPrototype action, string standardizedSearch,
private bool MatchesSearchCriteria(ActionType action, string standardizedSearch,
IReadOnlyList<string> selectedFilterTags)
{
// check filter tag match first - each action must contain all filter tags currently selected.
@@ -377,56 +306,32 @@ namespace Content.Client.Actions.UI
return true;
}
if (Standardize(ActionTypeString(action)).Contains(standardizedSearch))
{
return true;
}
// allows matching by typing spaces between the enum case changes, like "xeno spit" if the
// actiontype is "XenoSpit"
if (Standardize(ActionTypeString(action), true).Contains(standardizedSearch))
{
return true;
}
if (Standardize(action.Name.ToString()).Contains(standardizedSearch))
{
return true;
}
return false;
// search by provider name
if (action.Provider == null || action.Provider == _actionsUI.Component.Owner)
return false;
var name = _entMan.GetComponent<MetaDataComponent>(action.Provider.Value).EntityName;
return Standardize(name).Contains(standardizedSearch);
}
private string ActionTypeString(BaseActionPrototype baseActionPrototype)
{
if (baseActionPrototype is ActionPrototype actionPrototype)
{
return actionPrototype.ActionType.ToString();
}
if (baseActionPrototype is ItemActionPrototype itemActionPrototype)
{
return itemActionPrototype.ActionType.ToString();
}
throw new InvalidOperationException();
}
private bool ActionMatchesFilterTag(BaseActionPrototype action, string tag)
private bool ActionMatchesFilterTag(ActionType action, string tag)
{
return tag switch
{
AllActionsTag => true,
GrantedActionsTag => _actionsComponent.IsGranted(action),
ItemTag => action is ItemActionPrototype,
NotItemTag => action is ActionPrototype,
InstantActionTag => action.BehaviorType == BehaviorType.Instant,
TargetActionTag => action.IsTargetAction,
ToggleActionTag => action.BehaviorType == BehaviorType.Toggle,
_ => action.Filters.Contains(tag)
EnabledFilter => action.Enabled,
ItemFilter => action.Provider != null && action.Provider != _actionsUI.Component.Owner,
InnateFilter => action.Provider == null || action.Provider == _actionsUI.Component.Owner,
InstantFilter => action is InstantAction,
TargetedFilter => action is TargetedAction,
_ => true
};
}
/// <summary>
/// Standardized form is all lowercase, no non-alphanumeric characters (converted to whitespace),
/// trimmed, 1 space max per whitespace gap,
@@ -472,16 +377,15 @@ namespace Content.Client.Actions.UI
return newText.ToString();
}
private void PopulateActions(IEnumerable<BaseActionPrototype> actions)
private void PopulateActions(IEnumerable<ActionType> actions)
{
ClearList();
_actionList = actions.ToArray();
foreach (var action in _actionList.OrderBy(act => act.Name.ToString()))
foreach (var action in actions)
{
var actionItem = new ActionMenuItem(action, OnItemFocusExited);
var actionItem = new ActionMenuItem(_actionsUI, action, OnItemFocusExited);
_resultsGrid.Children.Add(actionItem);
actionItem.SetActionState(_actionsComponent.IsGranted(action));
actionItem.SetActionState(action.Enabled);
actionItem.OnButtonDown += OnItemButtonDown;
actionItem.OnButtonUp += OnItemButtonUp;
actionItem.OnPressed += OnItemPressed;
@@ -496,7 +400,6 @@ namespace Content.Client.Actions.UI
((ActionMenuItem) actionItem).OnPressed -= OnItemPressed;
}
_resultsGrid.Children.Clear();
_actionList = EmptyActionList;
}
/// <summary>
@@ -508,8 +411,10 @@ namespace Content.Client.Actions.UI
foreach (var actionItem in _resultsGrid.Children)
{
var actionMenuItem = ((ActionMenuItem) actionItem);
actionMenuItem.SetActionState(_actionsComponent.IsGranted(actionMenuItem.Action));
actionMenuItem.SetActionState(actionMenuItem.Action.Enabled);
}
SearchAndDisplay();
}
protected override void FrameUpdate(FrameEventArgs args)

View File

@@ -1,12 +1,20 @@
using System;
using System;
using Content.Client.Stylesheets;
using Content.Shared.Actions.Prototypes;
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.Utility;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Actions.UI
{
// TODO merge this with action-slot when it gets XAMLd
// this has way too much overlap, especially now that they both have the item-sprite icons.
/// <summary>
/// An individual action visible in the action menu.
/// </summary>
@@ -16,28 +24,165 @@ namespace Content.Client.Actions.UI
// quickly explore what each action is
private const float CustomTooltipDelay = 0.2f;
public BaseActionPrototype Action { get; private set; }
private readonly TextureRect _bigActionIcon;
private readonly TextureRect _smallActionIcon;
private readonly SpriteView _smallItemSpriteView;
private readonly SpriteView _bigItemSpriteView;
public ActionType Action;
private Action<ActionMenuItem> _onControlFocusExited;
public ActionMenuItem(BaseActionPrototype action, Action<ActionMenuItem> onControlFocusExited)
private readonly ActionsUI _actionsUI;
public ActionMenuItem(ActionsUI actionsUI, ActionType action, Action<ActionMenuItem> onControlFocusExited)
{
_onControlFocusExited = onControlFocusExited;
_actionsUI = actionsUI;
Action = action;
_onControlFocusExited = onControlFocusExited;
MinSize = (64, 64);
VerticalAlignment = VAlignment.Top;
AddChild(new TextureRect
_bigActionIcon = new TextureRect
{
HorizontalExpand = true,
VerticalExpand = true,
Stretch = TextureRect.StretchMode.Scale,
Texture = action.Icon.Frame0()
Visible = false
};
_bigItemSpriteView = new SpriteView
{
HorizontalExpand = true,
VerticalExpand = true,
Scale = (2, 2),
Visible = false,
OverrideDirection = Direction.South,
};
_smallActionIcon = new TextureRect
{
HorizontalAlignment = HAlignment.Right,
VerticalAlignment = VAlignment.Bottom,
Stretch = TextureRect.StretchMode.Scale,
Visible = false
};
_smallItemSpriteView = new SpriteView
{
HorizontalAlignment = HAlignment.Right,
VerticalAlignment = VAlignment.Bottom,
Visible = false,
OverrideDirection = Direction.South,
};
// padding to the left of the small icon
var paddingBoxItemIcon = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
HorizontalExpand = true,
VerticalExpand = true,
MinSize = (64, 64)
};
paddingBoxItemIcon.AddChild(new Control()
{
MinSize = (32, 32),
});
paddingBoxItemIcon.AddChild(new Control
{
Children =
{
_smallActionIcon,
_smallItemSpriteView
}
});
AddChild(_bigActionIcon);
AddChild(_bigItemSpriteView);
AddChild(paddingBoxItemIcon);
TooltipDelay = CustomTooltipDelay;
TooltipSupplier = SupplyTooltip;
UpdateIcons();
}
public void UpdateIcons()
{
UpdateItemIcon();
if (Action == null)
{
SetActionIcon(null);
return;
}
if ((_actionsUI.SelectingTargetFor?.Action == Action || Action.Toggled) && Action.IconOn != null)
SetActionIcon(Action.IconOn.Frame0());
else
SetActionIcon(Action.Icon?.Frame0());
}
private void SetActionIcon(Texture? texture)
{
if (texture == null || Action == null)
{
_bigActionIcon.Texture = null;
_bigActionIcon.Visible = false;
_smallActionIcon.Texture = null;
_smallActionIcon.Visible = false;
}
else if (Action.Provider != null && Action.ItemIconStyle == ItemActionIconStyle.BigItem)
{
_smallActionIcon.Texture = texture;
_smallActionIcon.Modulate = Action.IconColor;
_smallActionIcon.Visible = true;
_bigActionIcon.Texture = null;
_bigActionIcon.Visible = false;
}
else
{
_bigActionIcon.Texture = texture;
_bigActionIcon.Modulate = Action.IconColor;
_bigActionIcon.Visible = true;
_smallActionIcon.Texture = null;
_smallActionIcon.Visible = false;
}
}
private void UpdateItemIcon()
{
if (Action?.Provider == null || !IoCManager.Resolve<IEntityManager>().TryGetComponent(Action.Provider.Value, out SpriteComponent sprite))
{
_bigItemSpriteView.Visible = false;
_bigItemSpriteView.Sprite = null;
_smallItemSpriteView.Visible = false;
_smallItemSpriteView.Sprite = null;
}
else
{
switch (Action.ItemIconStyle)
{
case ItemActionIconStyle.BigItem:
_bigItemSpriteView.Visible = true;
_bigItemSpriteView.Sprite = sprite;
_smallItemSpriteView.Visible = false;
_smallItemSpriteView.Sprite = null;
break;
case ItemActionIconStyle.BigAction:
_bigItemSpriteView.Visible = false;
_bigItemSpriteView.Sprite = null;
_smallItemSpriteView.Visible = true;
_smallItemSpriteView.Sprite = sprite;
break;
case ItemActionIconStyle.NoItem:
_bigItemSpriteView.Visible = false;
_bigItemSpriteView.Sprite = null;
_smallItemSpriteView.Visible = false;
_smallItemSpriteView.Sprite = null;
break;
}
}
}
protected override void ControlFocusExited()
@@ -48,7 +193,15 @@ namespace Content.Client.Actions.UI
private Control SupplyTooltip(Control? sender)
{
return new ActionAlertTooltip(Action.Name, Action.Description, Action.Requires);
var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(Action.Name));
var decr = FormattedMessage.FromMarkupPermissive(Loc.GetString(Action.Description));
var tooltip = new ActionAlertTooltip(name, decr);
if (Action.Enabled && (Action.Charges == null || Action.Charges != 0))
tooltip.Cooldown = Action.Cooldown;
return tooltip;
}
/// <summary>
@@ -71,6 +224,5 @@ namespace Content.Client.Actions.UI
}
}
}
}
}

View File

@@ -1,20 +1,14 @@
using System;
using System;
using Content.Client.Cooldown;
using Content.Client.Stylesheets;
using Content.Shared.Actions;
using Content.Shared.Actions.Components;
using Content.Shared.Actions.Prototypes;
using Content.Shared.Inventory;
using Content.Shared.Actions.ActionTypes;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.Utility;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BoxContainer;
@@ -37,48 +31,7 @@ namespace Content.Client.Actions.UI
/// <summary>
/// Current action in this slot.
/// </summary>
public BaseActionPrototype? Action { get; private set; }
/// <summary>
/// true if there is an action assigned to the slot
/// </summary>
public bool HasAssignment => Action != null;
private bool HasToggleSprite => Action != null && Action.IconOn != SpriteSpecifier.Invalid;
/// <summary>
/// Only applicable when an action is in this slot.
/// True if the action is currently shown as enabled, false if action disabled.
/// </summary>
public bool ActionEnabled { get; private set; }
/// <summary>
/// Is there an action in the slot that can currently be used?
/// Target-basedActions on cooldown can still be selected / deselected if they've been configured as such
/// </summary>
public bool CanUseAction => Action != null && ActionEnabled &&
(!IsOnCooldown || (Action.IsTargetAction && !Action.DeselectOnCooldown));
/// <summary>
/// Item the action is provided by, only valid if Action is an ItemActionPrototype. May be null
/// if the item action is not yet tied to an item.
/// </summary>
public EntityUid? Item { get; private set; }
/// <summary>
/// Whether the action in this slot should be shown as toggled on. Separate from Depressed.
/// </summary>
public bool ToggledOn
{
get => _toggledOn;
set
{
if (_toggledOn == value) return;
_toggledOn = value;
UpdateIcons();
DrawModeChanged();
}
}
public ActionType? Action { get; private set; }
/// <summary>
/// 1-10 corresponding to the number label on the slot (10 is labeled as 0)
@@ -86,25 +39,6 @@ namespace Content.Client.Actions.UI
private byte SlotNumber => (byte) (SlotIndex + 1);
public byte SlotIndex { get; }
/// <summary>
/// Current cooldown displayed in this slot. Set to null to show no cooldown.
/// </summary>
public (TimeSpan Start, TimeSpan End)? Cooldown
{
get => _cooldown;
set
{
_cooldown = value;
if (SuppliedTooltip is ActionAlertTooltip actionAlertTooltip)
{
actionAlertTooltip.Cooldown = value;
}
}
}
private (TimeSpan Start, TimeSpan End)? _cooldown;
public bool IsOnCooldown => Cooldown.HasValue && _gameTiming.CurTime < Cooldown.Value.End;
private readonly IGameTiming _gameTiming;
private readonly RichTextLabel _number;
private readonly TextureRect _bigActionIcon;
@@ -114,8 +48,6 @@ namespace Content.Client.Actions.UI
private readonly CooldownGraphic _cooldownGraphic;
private readonly ActionsUI _actionsUI;
private readonly ActionMenu _actionMenu;
private readonly ClientActionsComponent _actionsComponent;
private bool _toggledOn;
// whether button is currently pressed down by mouse or keybind down.
private bool _depressed;
private bool _beingHovered;
@@ -124,9 +56,8 @@ namespace Content.Client.Actions.UI
/// Creates an action slot for the specified number
/// </summary>
/// <param name="slotIndex">slot index this corresponds to, 0-9 (0 labeled as 1, 8, labeled "9", 9 labeled as "0".</param>
public ActionSlot(ActionsUI actionsUI, ActionMenu actionMenu, ClientActionsComponent actionsComponent, byte slotIndex)
public ActionSlot(ActionsUI actionsUI, ActionMenu actionMenu, byte slotIndex)
{
_actionsComponent = actionsComponent;
_actionsUI = actionsUI;
_actionMenu = actionMenu;
_gameTiming = IoCManager.Resolve<IGameTiming>();
@@ -170,7 +101,8 @@ namespace Content.Client.Actions.UI
{
HorizontalAlignment = HAlignment.Right,
VerticalAlignment = VAlignment.Bottom,
Visible = false
Visible = false,
OverrideDirection = Direction.South,
};
_cooldownGraphic = new CooldownGraphic {Progress = 0, Visible = false};
@@ -219,24 +151,24 @@ namespace Content.Client.Actions.UI
private Control? SupplyTooltip(Control sender)
{
return Action == null ? null :
new ActionAlertTooltip(Action.Name, Action.Description, Action.Requires) {Cooldown = Cooldown};
}
if (Action == null)
return null;
/// <summary>
/// Action attempt for performing the action in the slot
/// </summary>
public IActionAttempt? ActionAttempt()
{
IActionAttempt? attempt = Action switch
string? extra = null;
if (Action.Charges != null)
{
ActionPrototype actionPrototype => new ActionAttempt(actionPrototype),
ItemActionPrototype itemActionPrototype =>
Item.HasValue && IoCManager.Resolve<IEntityManager>().TryGetComponent<ItemActionsComponent?>(Item, out var itemActions) ?
new ItemActionAttempt(itemActionPrototype, Item.Value, itemActions) : null,
_ => null
};
return attempt;
extra = Loc.GetString("ui-actionslot-charges", ("charges", Action.Charges));
}
var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(Action.Name));
var decr = FormattedMessage.FromMarkupPermissive(Loc.GetString(Action.Description));
var tooltip = new ActionAlertTooltip(name, decr, extra);
if (Action.Enabled && (Action.Charges == null || Action.Charges != 0))
tooltip.Cooldown = Action.Cooldown;
return tooltip;
}
protected override void MouseEntered()
@@ -245,9 +177,9 @@ namespace Content.Client.Actions.UI
_beingHovered = true;
DrawModeChanged();
if (Action is not ItemActionPrototype) return;
if (Item == null) return;
_actionsComponent.HighlightItemSlot(Item.Value);
if (Action?.Provider != null)
_actionsUI.System.HighlightItemSlot(Action.Provider.Value);
}
protected override void MouseExited()
@@ -256,32 +188,55 @@ namespace Content.Client.Actions.UI
_beingHovered = false;
CancelPress();
DrawModeChanged();
_actionsComponent.StopHighlightingItemSlots();
_actionsUI.System.StopHighlightingItemSlot();
}
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
{
base.KeyBindDown(args);
if (args.Function == EngineKeyFunctions.UIRightClick)
if (Action == null)
{
if (!_actionsUI.Locked && !_actionsUI.DragDropHelper.IsDragging && !_actionMenu.IsDragging)
{
_actionsComponent.Assignments.ClearSlot(_actionsUI.SelectedHotbar, SlotIndex, true);
_actionsUI.StopTargeting();
_actionsUI.UpdateUI();
}
// No action for this slot. Maybe the user is trying to add a mapping action?
_actionsUI.System.TryFillSlot(_actionsUI.SelectedHotbar, SlotIndex);
return;
}
// only handle clicks, and can't do anything to this if no assignment
if (args.Function != EngineKeyFunctions.UIClick || !HasAssignment)
if (args.Function == EngineKeyFunctions.UIClick)
{
// might turn into a drag or a full press if released
Depress(true);
_actionsUI.DragDropHelper.MouseDown(this);
DrawModeChanged();
return;
}
if (args.Function != EngineKeyFunctions.UIRightClick || _actionsUI.Locked)
return;
// might turn into a drag or a full press if released
Depress(true);
_actionsUI.DragDropHelper.MouseDown(this);
DrawModeChanged();
if (_actionsUI.DragDropHelper.IsDragging || _actionMenu.IsDragging)
return;
// user right clicked on an action slot, so we clear it.
_actionsUI.System.Assignments.ClearSlot(_actionsUI.SelectedHotbar, SlotIndex, true);
// If this was a temporary action, and it is no longer assigned to any slots, then we remove the action
// altogether.
if (Action.Temporary)
{
// Theres probably a better way to do this.....
DebugTools.Assert(Action.ClientExclusive, "Temporary-actions must be client exclusive");
if (!_actionsUI.System.Assignments.Assignments.TryGetValue(Action, out var index)
|| index.Count == 0)
{
_actionsUI.Component.Actions.Remove(Action);
}
}
_actionsUI.StopTargeting();
_actionsUI.UpdateUI();
}
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
@@ -299,21 +254,21 @@ namespace Content.Client.Actions.UI
{
// finish the drag, swap the 2 slots
var fromIdx = SlotIndex;
var fromAssignment = _actionsComponent.Assignments[_actionsUI.SelectedHotbar, fromIdx];
var fromAssignment = _actionsUI.System.Assignments[_actionsUI.SelectedHotbar, fromIdx];
var toIdx = targetSlot.SlotIndex;
var toAssignment = _actionsComponent.Assignments[_actionsUI.SelectedHotbar, toIdx];
var toAssignment = _actionsUI.System.Assignments[_actionsUI.SelectedHotbar, toIdx];
if (fromIdx == toIdx) return;
if (!fromAssignment.HasValue) return;
if (fromAssignment == null) return;
_actionsComponent.Assignments.AssignSlot(_actionsUI.SelectedHotbar, toIdx, fromAssignment.Value);
if (toAssignment.HasValue)
_actionsUI.System.Assignments.AssignSlot(_actionsUI.SelectedHotbar, toIdx, fromAssignment);
if (toAssignment != null)
{
_actionsComponent.Assignments.AssignSlot(_actionsUI.SelectedHotbar, fromIdx, toAssignment.Value);
_actionsUI.System.Assignments.AssignSlot(_actionsUI.SelectedHotbar, fromIdx, toAssignment);
}
else
{
_actionsComponent.Assignments.ClearSlot(_actionsUI.SelectedHotbar, fromIdx, false);
_actionsUI.System.Assignments.ClearSlot(_actionsUI.SelectedHotbar, fromIdx, false);
}
_actionsUI.UpdateUI();
}
@@ -348,66 +303,19 @@ namespace Content.Client.Actions.UI
/// <summary>
/// Press this button down. If it was depressed and now set to not depressed, will
/// trigger the action. Only has an effect if CanUseAction.
/// trigger the action.
/// </summary>
public void Depress(bool depress)
{
// action can still be toggled if it's allowed to stay selected
if (!CanUseAction) return;
if (Action == null || !Action.Enabled) return;
if (_depressed && !depress)
{
// fire the action
// no left-click interaction with it on cooldown or revoked
_actionsComponent.AttemptAction(this);
_actionsUI.System.OnSlotPressed(this);
}
_depressed = depress;
DrawModeChanged();
}
/// <summary>
/// Updates the action assigned to this slot.
/// </summary>
/// <param name="action">action to assign</param>
/// <param name="actionEnabled">whether action should initially appear enable or disabled</param>
public void Assign(ActionPrototype action, bool actionEnabled)
{
// already assigned
if (Action != null && Action == action) return;
Action = action;
Item = default;
_depressed = false;
ToggledOn = false;
ActionEnabled = actionEnabled;
Cooldown = null;
HideTooltip();
UpdateIcons();
DrawModeChanged();
_number.SetMessage(SlotNumberLabel());
}
/// <summary>
/// Updates the item action assigned to this slot. The action will always be shown as disabled
/// until it is tied to a specific item.
/// </summary>
/// <param name="action">action to assign</param>
public void Assign(ItemActionPrototype action)
{
// already assigned
if (Action != null && Action == action && Item == default) return;
Action = action;
Item = default;
_depressed = false;
ToggledOn = false;
ActionEnabled = false;
Cooldown = null;
HideTooltip();
UpdateIcons();
DrawModeChanged();
_number.SetMessage(SlotNumberLabel());
}
/// <summary>
@@ -415,18 +323,12 @@ namespace Content.Client.Actions.UI
/// </summary>
/// <param name="action">action to assign</param>
/// <param name="item">item the action is provided by</param>
/// <param name="actionEnabled">whether action should initially appear enable or disabled</param>
public void Assign(ItemActionPrototype action, EntityUid item, bool actionEnabled)
public void Assign(ActionType action)
{
// already assigned
if (Action != null && Action == action && Item == item) return;
if (Action != null && Action == action) return;
Action = action;
Item = item;
_depressed = false;
ToggledOn = false;
ActionEnabled = false;
Cooldown = null;
HideTooltip();
UpdateIcons();
DrawModeChanged();
@@ -438,12 +340,9 @@ namespace Content.Client.Actions.UI
/// </summary>
public void Clear()
{
if (!HasAssignment) return;
if (Action == null) return;
Action = null;
Item = default;
ToggledOn = false;
_depressed = false;
Cooldown = null;
HideTooltip();
UpdateIcons();
DrawModeChanged();
@@ -453,12 +352,8 @@ namespace Content.Client.Actions.UI
/// <summary>
/// Display the action in this slot (if there is one) as enabled
/// </summary>
public void EnableAction()
public void Enable()
{
if (ActionEnabled || !HasAssignment) return;
ActionEnabled = true;
_depressed = false;
DrawModeChanged();
_number.SetMessage(SlotNumberLabel());
}
@@ -467,11 +362,8 @@ namespace Content.Client.Actions.UI
/// Display the action in this slot (if there is one) as disabled.
/// The slot is still clickable.
/// </summary>
public void DisableAction()
public void Disable()
{
if (!ActionEnabled || !HasAssignment) return;
ActionEnabled = false;
_depressed = false;
DrawModeChanged();
_number.SetMessage(SlotNumberLabel());
@@ -481,70 +373,56 @@ namespace Content.Client.Actions.UI
{
if (SlotNumber > 10) return FormattedMessage.FromMarkup("");
var number = Loc.GetString(SlotNumber == 10 ? "0" : SlotNumber.ToString());
var color = (ActionEnabled || !HasAssignment) ? EnabledColor : DisabledColor;
var color = (Action == null || Action.Enabled) ? EnabledColor : DisabledColor;
return FormattedMessage.FromMarkup("[color=" + color + "]" + number + "[/color]");
}
private void UpdateIcons()
public void UpdateIcons()
{
if (!HasAssignment)
UpdateItemIcon();
if (Action == null)
{
SetActionIcon(null);
SetItemIcon(null);
return;
}
if (HasToggleSprite && ToggledOn && Action != null)
{
if ((_actionsUI.SelectingTargetFor?.Action == Action || Action.Toggled) && Action.IconOn != null)
SetActionIcon(Action.IconOn.Frame0());
}
else if (Action != null)
{
SetActionIcon(Action.Icon.Frame0());
}
if (Item != default)
{
SetItemIcon(IoCManager.Resolve<IEntityManager>().TryGetComponent<ISpriteComponent?>(Item, out var spriteComponent) ? spriteComponent : null);
}
else
{
SetItemIcon(null);
}
SetActionIcon(Action.Icon?.Frame0());
}
private void SetActionIcon(Texture? texture)
{
if (texture == null || !HasAssignment)
if (texture == null || Action == null)
{
_bigActionIcon.Texture = null;
_bigActionIcon.Visible = false;
_smallActionIcon.Texture = null;
_smallActionIcon.Visible = false;
}
else if (Action.Provider != null && Action.ItemIconStyle == ItemActionIconStyle.BigItem)
{
_smallActionIcon.Texture = texture;
_smallActionIcon.Modulate = Action.IconColor;
_smallActionIcon.Visible = true;
_bigActionIcon.Texture = null;
_bigActionIcon.Visible = false;
}
else
{
if (Action is ItemActionPrototype {IconStyle: ItemActionIconStyle.BigItem})
{
_bigActionIcon.Texture = null;
_bigActionIcon.Visible = false;
_smallActionIcon.Texture = texture;
_smallActionIcon.Visible = true;
}
else
{
_bigActionIcon.Texture = texture;
_bigActionIcon.Visible = true;
_smallActionIcon.Texture = null;
_smallActionIcon.Visible = false;
}
_bigActionIcon.Texture = texture;
_bigActionIcon.Modulate = Action.IconColor;
_bigActionIcon.Visible = true;
_smallActionIcon.Texture = null;
_smallActionIcon.Visible = false;
}
}
private void SetItemIcon(ISpriteComponent? sprite)
private void UpdateItemIcon()
{
if (sprite == null || !HasAssignment)
if (Action?.Provider == null || !IoCManager.Resolve<IEntityManager>().TryGetComponent(Action.Provider.Value, out SpriteComponent sprite))
{
_bigItemSpriteView.Visible = false;
_bigItemSpriteView.Sprite = null;
@@ -553,70 +431,48 @@ namespace Content.Client.Actions.UI
}
else
{
if (Action is ItemActionPrototype actionPrototype)
switch (Action.ItemIconStyle)
{
switch (actionPrototype.IconStyle)
{
case ItemActionIconStyle.BigItem:
{
_bigItemSpriteView.Visible = true;
_bigItemSpriteView.Sprite = sprite;
_smallItemSpriteView.Visible = false;
_smallItemSpriteView.Sprite = null;
break;
}
case ItemActionIconStyle.BigAction:
{
_bigItemSpriteView.Visible = false;
_bigItemSpriteView.Sprite = null;
_smallItemSpriteView.Visible = true;
_smallItemSpriteView.Sprite = sprite;
break;
}
case ItemActionIconStyle.NoItem:
{
_bigItemSpriteView.Visible = false;
_bigItemSpriteView.Sprite = null;
_smallItemSpriteView.Visible = false;
_smallItemSpriteView.Sprite = null;
break;
}
}
case ItemActionIconStyle.BigItem:
_bigItemSpriteView.Visible = true;
_bigItemSpriteView.Sprite = sprite;
_smallItemSpriteView.Visible = false;
_smallItemSpriteView.Sprite = null;
break;
case ItemActionIconStyle.BigAction:
}
else
{
_bigItemSpriteView.Visible = false;
_bigItemSpriteView.Sprite = null;
_smallItemSpriteView.Visible = false;
_smallItemSpriteView.Sprite = null;
}
_bigItemSpriteView.Visible = false;
_bigItemSpriteView.Sprite = null;
_smallItemSpriteView.Visible = true;
_smallItemSpriteView.Sprite = sprite;
break;
case ItemActionIconStyle.NoItem:
_bigItemSpriteView.Visible = false;
_bigItemSpriteView.Sprite = null;
_smallItemSpriteView.Visible = false;
_smallItemSpriteView.Sprite = null;
break;
}
}
}
private void DrawModeChanged()
public void DrawModeChanged()
{
// show a hover only if the action is usable or another action is being dragged on top of this
if (_beingHovered)
{
if (_actionsUI.DragDropHelper.IsDragging || _actionMenu.IsDragging ||
(HasAssignment && ActionEnabled && !IsOnCooldown))
{
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassHover);
return;
}
}
// always show the normal empty button style if no action in this slot
if (!HasAssignment)
if (Action == null)
{
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal);
return;
}
// show a hover only if the action is usable or another action is being dragged on top of this
if (_beingHovered && (_actionsUI.DragDropHelper.IsDragging || _actionMenu.IsDragging || Action.Enabled))
{
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassHover);
}
// it's only depress-able if it's usable, so if we're depressed
// show the depressed style
if (_depressed)
@@ -625,47 +481,50 @@ namespace Content.Client.Actions.UI
return;
}
// if it's toggled on, always show the toggled on style (currently same as depressed style)
if (ToggledOn)
if (Action.Toggled || _actionsUI.SelectingTargetFor == this)
{
// when there's a toggle sprite, we're showing that sprite instead of highlighting this slot
SetOnlyStylePseudoClass(HasToggleSprite ? ContainerButton.StylePseudoClassNormal :
SetOnlyStylePseudoClass(Action.IconOn != null ? ContainerButton.StylePseudoClassNormal :
ContainerButton.StylePseudoClassPressed);
return;
}
if (!ActionEnabled)
if (!Action.Enabled)
{
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassDisabled);
return;
}
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal);
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (!Cooldown.HasValue)
if (Action == null || Action.Cooldown == null || !Action.Enabled)
{
_cooldownGraphic.Visible = false;
_cooldownGraphic.Progress = 0;
return;
}
var duration = Cooldown.Value.End - Cooldown.Value.Start;
var cooldown = Action.Cooldown.Value;
var duration = cooldown.End - cooldown.Start;
var curTime = _gameTiming.CurTime;
var length = duration.TotalSeconds;
var progress = (curTime - Cooldown.Value.Start).TotalSeconds / length;
var ratio = (progress <= 1 ? (1 - progress) : (curTime - Cooldown.Value.End).TotalSeconds * -5);
var progress = (curTime - cooldown.Start).TotalSeconds / length;
var ratio = (progress <= 1 ? (1 - progress) : (curTime - cooldown.End).TotalSeconds * -5);
_cooldownGraphic.Progress = MathHelper.Clamp((float)ratio, -1, 1);
_cooldownGraphic.Visible = ratio > -1f;
if (ratio > -1f)
_cooldownGraphic.Visible = true;
else
{
_cooldownGraphic.Visible = false;
Action.Cooldown = null;
DrawModeChanged();
}
}
}
}

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,61 @@ 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))
if (Component.Actions.TryGetValue(action, out var actualAction))
{
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();
UpdateActionSlot(actualAction, actionSlot);
continue;
}
// Action not in the actions component, but in the assignment list.
// This is either an action that doesn't auto-clear from the menu, or the action menu was locked.
// Show the old action, but make sure it is disabled;
action.Enabled = false;
action.Toggled = false;
// If we enable the item-sprite, and if the item-sprite has a visual toggle, then the player will be
// able to know whether the item is toggled, even if it is not in their LOS (but in PVS). And for things
// like PDA sprites, the player can even see whether the action's item is currently inside of their PVS.
// SO unless theres some way of "freezing" a sprite-view, we just have to disable it.
action.ItemIconStyle = ItemActionIconStyle.NoItem;
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 +345,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 +383,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 +419,6 @@ namespace Content.Client.Actions.UI
ToggleActionsMenu();
}
private void OnToggleActionsMenuTopButton(bool open)
{
if (open == _menu.IsOpen) return;
@@ -543,7 +448,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 +479,7 @@ namespace Content.Client.Actions.UI
{
var actionSlot = _slots[slot];
actionSlot.Depress(args.State == BoundKeyState.Down);
actionSlot.DrawModeChanged();
}
/// <summary>