From 6cb58e608b8a2c484f0064c1d53bf3bd186cbb71 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Tue, 5 Oct 2021 14:29:03 +1100 Subject: [PATCH] ECS verbs and update context menu (#4594) * Functioning ECS verbs Currently only ID card console works. * Changed verb types and allow ID card insertions * Verb GUI sorting and verb networking * More networking, and shared components * Clientside verbs work now. * Verb enums changed to bitmask flags * Verb Categories redo * Fix range check * GasTank Verb * Remove unnecessary bodypart verb * Buckle Verb * buckle & unbuckle verbs * Updated range checks * Item cabinet verbs * Add range user override * construction verb * Chemistry machine verbs * Climb Verb * Generalise pulled entity verbs * ViewVariables Verb * rejuvenate, delete, sentient, control verbs * Outfit verb * inrangeunoccluded and tubedirection verbs * attach-to verbs * remove unused verbs and move VV * Rename DebugVerbSystem * Ghost role and pointing verbs * Remove global verbs * Allow verbs to raise events * Changing categories and simplifying debug verbs * Add rotate and flip verbs * fix rejuvenate test * redo context menu * new Add Gas debug verb * Add Set Temperature debug verb * Uncuff verb * Disposal unit verbs * Add pickup verb * lock/unlock verb * Remove verb type, add specific verb events * rename verb messages -> events * Context menu displays verbs by interaction type * Updated context menu HandleMove previously, checked if entities moved 1 tile from click location. Now checks if entities moved out of view. Now you can actually right-click interact with yourself while walking! * Misc Verb menu GUI changes * Fix non-human/ghost verbs * Update types and categories * Allow non-ghost/human to open context menu * configuration verb * tagger verb * Morgue Verbs * Medical Scanner Verbs * Fix solution refactor merge issues * Fix context menu in-view check * Remove prepare GUI * Redo verb restrictions * Fix context menu UI * Disposal Verbs * Spill verb * Light verb * Hand Held light verb * power cell verbs * storage verbs and adding names to insert/eject * Pulling verb * Close context menu on verb execution * Strip verb * AmmoBox verb * fix pull verb * gun barrel verbs revolver verb energy weapon verbs Bolt action verb * Magazine gun barrel verbs * Add charger verbs * PDA verbs * Transfer amount verb * Add reagent verb * make alt-click use ECS verbs * Delete old verb files * Magboot verb * finalising tweaks * context menu visibility changes * code cleanup * Update AdminAddReagentUI.cs * Remove HasFlag * Consistent verb keys * Remove Linq, add comment * Fix in-inventory check * Update GUI text alignment and padding * Added close-menu option * Changed some "interaction" verbs to "activation" * Remove verb keys, use sorted sets * fix master merge * update some verb text * Undo Changes Remove some new verbs that can be added later undid some .ftl bugfixes, can and should be done separately * fix merge * Undo file rename * fix merge * Misc Cleanup * remove contraction * Fix keybinding issue * fix comment * merge fix * fix merge * fix merge * fix merge * fix merge * fix open-close verbs * adjust uncuff verb * fix merge and undo the renaming of SharedPullableComponent to PullableComponent. I'm tired of all of those merge conflicts --- .../Administration/AdminVerbSystem.cs | 40 ++ .../ContextMenu/UI/ContextMenuElement.cs | 54 +- .../ContextMenu/UI/ContextMenuPresenter.cs | 161 +++-- .../ContextMenu/UI/ContextMenuView.cs | 3 +- .../ContextMenu/UI/ContextMenuViewGrouping.cs | 10 +- Content.Client/Examine/ExamineSystem.cs | 18 +- Content.Client/Examine/ExamineVerb.cs | 26 - Content.Client/Input/ContentContexts.cs | 4 +- .../Items/Managers/ItemSlotManager.cs | 2 +- Content.Client/Pulling/PullableComponent.cs | 11 - Content.Client/Pulling/PullingSystem.cs | 7 +- .../Rotatable/RotatableComponent.cs | 11 - Content.Client/Verbs/VerbMenuElement.cs | 246 ++++++++ Content.Client/Verbs/VerbSystem.cs | 571 ++++-------------- .../ViewVariables/ViewVariablesVerb.cs | 38 -- .../Tests/Commands/RejuvenateTest.cs | 6 +- .../Components/IdCardConsoleComponent.cs | 131 ++-- Content.Server/Access/IdCardConsoleSystem.cs | 80 +++ .../Administration/AdminVerbSystem.cs | 188 ++++++ .../{Rejuvenate.cs => RejuvenateCommand.cs} | 35 +- .../Administration/UI/AdminAddReagentEui.cs | 94 +++ .../Verbs/AdminAddReagentVerb.cs | 150 ----- .../Administration/Verbs/DeleteVerb.cs | 55 -- Content.Server/Alert/Click/StopBeingPulled.cs | 3 +- Content.Server/Alert/Click/StopPulling.cs | 2 +- .../Atmos/Components/GasTankComponent.cs | 32 - .../Atmos/EntitySystems/GasTankSystem.cs | 21 + .../Body/Commands/AttachBodyPartCommand.cs | 2 +- Content.Server/Body/Part/BodyPartComponent.cs | 49 -- .../Buckle/Components/BuckleComponent.cs | 33 +- .../Buckle/Components/StrapComponent.cs | 51 -- .../Buckle/{ => Systems}/BuckleSystem.cs | 25 +- Content.Server/Buckle/Systems/StrapSystem.cs | 101 ++++ .../Cabinet/ItemCabinetComponent.cs | 51 -- Content.Server/Cabinet/ItemCabinetSystem.cs | 103 +++- .../Components/ChemMasterComponent.cs | 43 +- .../Components/ReagentDispenserComponent.cs | 53 +- .../Components/SolutionTransferComponent.cs | 133 +--- .../EntitySystems/ChemMasterSystem.cs | 53 +- .../EntitySystems/ReagentDispenserSystem.cs | 63 +- .../EntitySystems/SolutionTransferSystem.cs | 68 +++ Content.Server/Climbing/ClimbSystem.cs | 27 +- .../Climbing/Components/ClimbableComponent.cs | 26 +- .../Clothing/Components/MagbootsComponent.cs | 20 - Content.Server/Clothing/MagbootsSystem.cs | 29 + .../Configurable/ConfigurationComponent.cs | 33 +- .../Components/AnchorableComponent.cs | 3 +- .../Components/ConstructionComponent.Verbs.cs | 49 -- .../Construction/ConstructionSystem.cs | 35 +- .../Commands/HideContainedContextCommand.cs | 29 - .../Cuffs/Components/CuffableComponent.cs | 26 - Content.Server/Cuffs/CuffableSystem.cs | 26 +- Content.Server/Damage/RejuvenateVerb.cs | 94 --- .../Components/DisposalRouterComponent.cs | 28 +- .../Components/DisposalTaggerComponent.cs | 29 +- .../Tube/Components/DisposalTubeComponent.cs | 34 -- .../Disposal/Tube/DisposalTubeSystem.cs | 38 ++ .../Unit/Components/DisposalUnitComponent.cs | 78 --- .../Unit/EntitySystems/DisposalUnitSystem.cs | 48 +- .../Fluids/Components/SpillableComponent.cs | 56 -- Content.Server/Fluids/PuddleSystem.cs | 24 + .../Ghost/Roles/MakeGhostRoleVerb.cs | 60 -- .../Hands/Components/HandsComponent.cs | 2 +- .../Interaction/InRangeUnoccludedVerb.cs | 58 -- .../Interaction/InteractionSystem.cs | 45 +- .../Components/InventoryComponent.cs | 42 -- Content.Server/Items/ItemComponent.cs | 32 - Content.Server/Items/ItemSystem.cs | 39 ++ .../Components/ExpendableLightComponent.cs | 34 +- .../Components/HandheldLightComponent.cs | 24 +- .../UnpoweredFlashlightComponent.cs | 22 - .../EntitySystems/ExpendableLightSystem.cs | 29 +- .../EntitySystems/HandHeldLightSystem.cs | 18 + .../UnpoweredFlashlightSystem.cs | 23 + Content.Server/Lock/LockComponent.cs | 45 -- Content.Server/Lock/LockSystem.cs | 66 +- .../Components/MedicalScannerComponent.cs | 46 -- .../Medical/MedicalScannerSystem.cs | 55 +- .../Mind/Commands/MakeSentientCommand.cs | 2 + Content.Server/Mind/Verbs/ControlMobVerb.cs | 62 -- Content.Server/Mind/Verbs/MakeSentientVerb.cs | 53 -- .../BodyBagEntityStorageComponent.cs | 24 - .../CrematoriumEntityStorageComponent.cs | 23 - Content.Server/Morgue/MorgueSystem.cs | 40 +- Content.Server/PDA/PDAComponent.cs | 76 --- Content.Server/PDA/PDASystem.cs | 3 +- .../Pointing/EntitySystems/PointingSystem.cs | 23 + Content.Server/Pointing/PointingVerb.cs | 53 -- .../Power/Components/BaseCharger.cs | 103 +--- .../Power/EntitySystems/BaseChargerSystem.cs | 51 ++ .../Components/PowerCellChargerComponent.cs | 2 +- .../Components/PowerCellSlotComponent.cs | 39 -- Content.Server/PowerCell/PowerCellSystem.cs | 42 +- Content.Server/Pulling/PullableComponent.cs | 63 -- Content.Server/Pulling/PullingSystem.cs | 4 +- .../Rotatable/FlippableComponent.cs | 19 + Content.Server/Rotatable/RotatableSystem.cs | 82 +++ .../Rotation/Components/FlippableComponent.cs | 60 -- .../Rotation/Components/RotatableComponent.cs | 75 --- Content.Server/Rotation/HardRotateVerbs.cs | 42 -- .../Components/EntityStorageComponent.cs | 60 +- .../Components/ServerStorageComponent.cs | 41 -- .../Storage/EntitySystems/StorageSystem.cs | 60 ++ Content.Server/Strip/StrippableComponent.cs | 29 +- Content.Server/Strip/StrippableSystem.cs | 32 + .../Components/TabletopGameComponent.cs | 29 - Content.Server/Tabletop/TabletopSystem.cs | 24 +- .../Verbs/AttachToGrandparentVerb.cs | 55 -- .../Transform/Verbs/AttachToGridVerb.cs | 54 -- .../Transform/Verbs/AttachToSelf.cs | 54 -- .../ToggleAllContextCommand.cs} | 8 +- Content.Server/Verbs/VerbSystem.cs | 166 ++--- .../Ranged/Ammunition/AmmunitionSystem.cs | 32 + .../Ammunition/Components/AmmoBoxComponent.cs | 33 +- .../Weapon/Ranged/Barrels/BarrelSystem.cs | 149 +++++ .../Components/BoltActionBarrelComponent.cs | 44 -- .../Components/RevolverBarrelComponent.cs | 31 - .../ServerBatteryBarrelComponent.cs | 43 +- .../ServerMagazineBarrelComponent.cs | 182 ++---- .../Weapon/WeaponCapacitorChargerComponent.cs | 2 +- .../Components/WieldableComponent.cs | 21 - Content.Server/Wieldable/WieldableSystem.cs | 22 +- .../Administration/AdminAddReagentEuiState.cs | 2 +- .../EntitySystems/SolutionContainerSystem.cs | 2 +- .../ItemSlot/SharedItemSlotsSystem.cs | 120 +++- Content.Shared/Item/SharedItemComponent.cs | 4 +- .../SharedExpendableLightComponent.cs | 2 +- .../SharedMedicalScannerComponent.cs | 6 +- ...lableComponent.cs => PullableComponent.cs} | 3 +- .../Pulling/Systems/SharedPullerSystem.cs | 2 +- .../Pulling/Systems/SharedPullingSystem.cs | 32 +- ...ableComponent.cs => RotatableComponent.cs} | 3 +- Content.Shared/Verbs/GlobalVerb.cs | 54 -- .../Verbs/HideContextMenuComponent.cs | 15 - Content.Shared/Verbs/IShowContextMenu.cs | 9 - .../Verbs/PlayerContainerVisibilityMessage.cs | 17 - Content.Shared/Verbs/SharedVerbSystem.cs | 140 ++++- Content.Shared/Verbs/Verb.cs | 232 ++++--- Content.Shared/Verbs/VerbBase.cs | 24 - Content.Shared/Verbs/VerbCategories.cs | 18 - Content.Shared/Verbs/VerbCategory.cs | 54 ++ Content.Shared/Verbs/VerbCategoryData.cs | 31 - Content.Shared/Verbs/VerbData.cs | 68 --- Content.Shared/Verbs/VerbEvents.cs | 211 +++++++ Content.Shared/Verbs/VerbSystemMessages.cs | 69 --- Content.Shared/Verbs/VerbUtility.cs | 99 --- Content.Shared/Verbs/VerbVisibility.cs | 24 - .../components/id-card-console-component.ftl | 10 +- .../body/components/bodypart-component.ftl | 3 - .../buckle/components/buckle-component.ftl | 3 - .../buckle/components/strap-component.ftl | 4 - .../en-US/cabinet/item-cabinet-system.ftl | 8 - .../components/chem-master-component.ftl | 3 - .../solution-transfer-component.ftl | 6 +- .../construction-component-verbs.ftl | 2 +- .../disposal-mailing-unit-component.ftl | 4 +- .../Locale/en-US/examine/examine-system.ftl | 4 +- .../Locale/en-US/examine/examine-verb.ftl | 1 - .../en-US/items/components/item-component.ftl | 6 +- .../components/expendable-light-component.ftl | 1 + .../components/handheld-light-component.ftl | 3 - .../components/medical-scanner-component.ftl | 7 +- .../components/power-cell-slot-component.ftl | 2 - .../components/power-receiver-component.ftl | 8 - .../components/entity-storage-component.ftl | 3 +- .../storage/components/storage-component.ftl | 5 - Resources/Locale/en-US/verbs/verb-system.ftl | 23 +- .../server-battery-barrel-component.ftl | 4 - .../server-magazine-barrel-component.ftl | 4 - .../Entities/Markers/drag_shadow.yml | 10 +- .../Prototypes/Entities/Markers/pointing.yml | 4 +- .../Structures/Piping/Disposal/pipes.yml | 8 +- .../Entities/Structures/Walls/low.yml | 4 +- Resources/Prototypes/tags.yml | 3 + Resources/keybinds.yml | 1 + 175 files changed, 3391 insertions(+), 4305 deletions(-) create mode 100644 Content.Client/Administration/AdminVerbSystem.cs delete mode 100644 Content.Client/Examine/ExamineVerb.cs delete mode 100644 Content.Client/Pulling/PullableComponent.cs delete mode 100644 Content.Client/Rotatable/RotatableComponent.cs create mode 100644 Content.Client/Verbs/VerbMenuElement.cs delete mode 100644 Content.Client/ViewVariables/ViewVariablesVerb.cs create mode 100644 Content.Server/Access/IdCardConsoleSystem.cs create mode 100644 Content.Server/Administration/AdminVerbSystem.cs rename Content.Server/Administration/Commands/{Rejuvenate.cs => RejuvenateCommand.cs} (53%) create mode 100644 Content.Server/Administration/UI/AdminAddReagentEui.cs delete mode 100644 Content.Server/Administration/Verbs/AdminAddReagentVerb.cs delete mode 100644 Content.Server/Administration/Verbs/DeleteVerb.cs rename Content.Server/Buckle/{ => Systems}/BuckleSystem.cs (81%) create mode 100644 Content.Server/Buckle/Systems/StrapSystem.cs create mode 100644 Content.Server/Chemistry/EntitySystems/SolutionTransferSystem.cs create mode 100644 Content.Server/Clothing/MagbootsSystem.cs delete mode 100644 Content.Server/Construction/Components/ConstructionComponent.Verbs.cs delete mode 100644 Content.Server/Containers/Commands/HideContainedContextCommand.cs delete mode 100644 Content.Server/Damage/RejuvenateVerb.cs delete mode 100644 Content.Server/Ghost/Roles/MakeGhostRoleVerb.cs delete mode 100644 Content.Server/Interaction/InRangeUnoccludedVerb.cs create mode 100644 Content.Server/Items/ItemSystem.cs delete mode 100644 Content.Server/Mind/Verbs/ControlMobVerb.cs delete mode 100644 Content.Server/Mind/Verbs/MakeSentientVerb.cs delete mode 100644 Content.Server/Pointing/PointingVerb.cs delete mode 100644 Content.Server/Pulling/PullableComponent.cs create mode 100644 Content.Server/Rotatable/FlippableComponent.cs create mode 100644 Content.Server/Rotatable/RotatableSystem.cs delete mode 100644 Content.Server/Rotation/Components/FlippableComponent.cs delete mode 100644 Content.Server/Rotation/Components/RotatableComponent.cs delete mode 100644 Content.Server/Rotation/HardRotateVerbs.cs create mode 100644 Content.Server/Strip/StrippableSystem.cs delete mode 100644 Content.Server/Transform/Verbs/AttachToGrandparentVerb.cs delete mode 100644 Content.Server/Transform/Verbs/AttachToGridVerb.cs delete mode 100644 Content.Server/Transform/Verbs/AttachToSelf.cs rename Content.Server/{Containers/Commands/ShowContainedContextCommand.cs => Verbs/ToggleAllContextCommand.cs} (69%) create mode 100644 Content.Server/Weapon/Ranged/Ammunition/AmmunitionSystem.cs create mode 100644 Content.Server/Weapon/Ranged/Barrels/BarrelSystem.cs rename Content.Shared/Pulling/Components/{SharedPullableComponent.cs => PullableComponent.cs} (97%) rename Content.Shared/Rotatable/{SharedRotatableComponent.cs => RotatableComponent.cs} (91%) delete mode 100644 Content.Shared/Verbs/GlobalVerb.cs delete mode 100644 Content.Shared/Verbs/HideContextMenuComponent.cs delete mode 100644 Content.Shared/Verbs/IShowContextMenu.cs delete mode 100644 Content.Shared/Verbs/PlayerContainerVisibilityMessage.cs delete mode 100644 Content.Shared/Verbs/VerbBase.cs delete mode 100644 Content.Shared/Verbs/VerbCategories.cs create mode 100644 Content.Shared/Verbs/VerbCategory.cs delete mode 100644 Content.Shared/Verbs/VerbCategoryData.cs delete mode 100644 Content.Shared/Verbs/VerbData.cs create mode 100644 Content.Shared/Verbs/VerbEvents.cs delete mode 100644 Content.Shared/Verbs/VerbSystemMessages.cs delete mode 100644 Content.Shared/Verbs/VerbUtility.cs delete mode 100644 Content.Shared/Verbs/VerbVisibility.cs delete mode 100644 Resources/Locale/en-US/buckle/components/strap-component.ftl delete mode 100644 Resources/Locale/en-US/examine/examine-verb.ftl create mode 100644 Resources/Locale/en-US/light/components/expendable-light-component.ftl delete mode 100644 Resources/Locale/en-US/storage/components/storage-component.ftl delete mode 100644 Resources/Locale/en-US/weapons/ranged/barrels/components/server-battery-barrel-component.ftl diff --git a/Content.Client/Administration/AdminVerbSystem.cs b/Content.Client/Administration/AdminVerbSystem.cs new file mode 100644 index 0000000000..5cc3300483 --- /dev/null +++ b/Content.Client/Administration/AdminVerbSystem.cs @@ -0,0 +1,40 @@ +using Content.Client.Administration.UI.Tabs.AtmosTab; +using Content.Shared.Verbs; +using Robust.Client.Console; +using Robust.Client.ViewVariables; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; + +namespace Content.Client.Verbs +{ + /// + /// Client-side admin verb system. These usually open some sort of UIs. + /// + class AdminVerbSystem : EntitySystem + { + [Dependency] private readonly IClientConGroupController _clientConGroupController = default!; + [Dependency] private readonly IViewVariablesManager _viewVariablesManager = default!; + + public override void Initialize() + { + SubscribeLocalEvent(AddAdminVerbs); + } + + private void AddAdminVerbs(GetOtherVerbsEvent args) + { + // Currently this is only the ViewVariables verb, but more admin-UI related verbs can be added here. + + // View variables verbs + if (_clientConGroupController.CanViewVar()) + { + Verb verb = new(); + verb.Category = VerbCategory.Debug; + verb.Text = "View Variables"; + verb.IconTexture = "/Textures/Interface/VerbIcons/vv.svg.192dpi.png"; + verb.Act = () => _viewVariablesManager.OpenVV(args.Target); + args.Verbs.Add(verb); + } + } + } +} diff --git a/Content.Client/ContextMenu/UI/ContextMenuElement.cs b/Content.Client/ContextMenu/UI/ContextMenuElement.cs index d2fa8b146d..b1d3b909ec 100644 --- a/Content.Client/ContextMenu/UI/ContextMenuElement.cs +++ b/Content.Client/ContextMenu/UI/ContextMenuElement.cs @@ -101,7 +101,6 @@ namespace Content.Client.ContextMenu.UI public sealed class StackContextElement : ContextMenuElement { public event Action? OnExitedTree; - public readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2); public HashSet ContextEntities { get; } public readonly StackContextElement? Pre; @@ -176,40 +175,44 @@ namespace Content.Client.ContextMenu.UI } } - public sealed class ContextMenuPopup : Robust.Client.UserInterface.Controls.Popup + public class ContextMenuPopup : Popup { - private static readonly Color DefaultColor = Color.FromHex("#1116"); - private static readonly Color MarginColor = Color.FromHex("#222E"); - private const int MaxItemsBeforeScroll = 10; - private const int MarginSizeBetweenElements = 2; + public static readonly Color ButtonColor = Color.FromHex("#1119"); + public static readonly Color BackgroundColor = Color.FromHex("#333E"); + + public const int MaxItemsBeforeScroll = 10; + public const int MarginSize = 2; + public const int ButtonHeight = 32; public BoxContainer List { get; } + public ScrollContainer Scroll { get; } public int Depth { get; } public ContextMenuPopup(int depth = 0) { + MaxHeight = MaxItemsBeforeScroll * (ButtonHeight + 2*MarginSize); + Depth = depth; - AddChild(new ScrollContainer + List = new() { Orientation = LayoutOrientation.Vertical }; + Scroll = new() { HScrollEnabled = false, - Children = { new PanelContainer - { - Children = { (List = new BoxContainer - { - Orientation = LayoutOrientation.Vertical - }) }, - PanelOverride = new StyleBoxFlat { BackgroundColor = MarginColor } - }} + Children = { List } + }; + AddChild(new PanelContainer + { + Children = { Scroll }, + PanelOverride = new StyleBoxFlat { BackgroundColor = BackgroundColor } }); } - public void AddToMenu(ContextMenuElement element) + public void AddToMenu(Control element) { List.AddChild(new PanelContainer { Children = { element }, - Margin = new Thickness(0,0,0, MarginSizeBetweenElements), - PanelOverride = new StyleBoxFlat {BackgroundColor = DefaultColor} + Margin = new Thickness(MarginSize, MarginSize, MarginSize, MarginSize), + PanelOverride = new StyleBoxFlat { BackgroundColor = ButtonColor } }); } @@ -229,15 +232,18 @@ namespace Content.Client.ContextMenu.UI return Vector2.Zero; } - List.Measure(availableSize); - var listSize = List.DesiredSize; + Scroll.Measure(availableSize); + var size = List.DesiredSize; - if (List.ChildCount < MaxItemsBeforeScroll) + // account for scroll bar width + if (size.Y > MaxHeight) { - return listSize; + // Scroll._vScrollBar is private and ScrollContainer gives no size information :/ + // 10 = Scroll._vScrollBar.DesiredSize + size.X += 10; } - listSize.Y = MaxItemsBeforeScroll * 32 + MaxItemsBeforeScroll * MarginSizeBetweenElements; - return listSize; + + return size; } } } diff --git a/Content.Client/ContextMenu/UI/ContextMenuPresenter.cs b/Content.Client/ContextMenu/UI/ContextMenuPresenter.cs index 1cc046b640..753f6c611b 100644 --- a/Content.Client/ContextMenu/UI/ContextMenuPresenter.cs +++ b/Content.Client/ContextMenu/UI/ContextMenuPresenter.cs @@ -6,10 +6,9 @@ using Content.Client.Interactable; using Content.Client.Items.Managers; using Content.Client.Verbs; using Content.Client.Viewport; -using Content.Shared; using Content.Shared.CCVar; using Content.Shared.Input; -using Content.Shared.Verbs; +using Content.Shared.Interaction.Helpers; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Input; @@ -40,21 +39,19 @@ namespace Content.Client.ContextMenu.UI [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IEyeManager _eyeManager = default!; + public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2); + private CancellationTokenSource? _cancelHover; + private readonly IContextMenuView _contextMenuView; private readonly VerbSystem _verbSystem; - private bool _playerCanSeeThroughContainers; - private MapCoordinates _mapCoordinates; - private CancellationTokenSource? _cancellationTokenSource; public ContextMenuPresenter(VerbSystem verbSystem) { IoCManager.InjectDependencies(this); _verbSystem = verbSystem; - _verbSystem.ToggleContextMenu += SystemOnToggleContextMenu; - _verbSystem.ToggleContainerVisibility += SystemOnToggleContainerVisibility; _contextMenuView = new ContextMenuView(); _contextMenuView.OnKeyBindDownSingle += OnKeyBindDownSingle; @@ -70,6 +67,10 @@ namespace Content.Client.ContextMenu.UI _contextMenuView.OnCloseChildMenu += OnCloseChildMenu; _cfg.OnValueChanged(CCVars.ContextMenuGroupingType, _contextMenuView.OnGroupingContextMenuChanged, true); + + CommandBinds.Builder + .Bind(ContentKeyFunctions.OpenContextMenu, new PointerInputCmdHandler(HandleOpenContextMenu)) + .Register(); } #region View Events @@ -92,13 +93,11 @@ namespace Content.Client.ContextMenu.UI { var realGlobalPosition = e.GlobalPosition; - _cancellationTokenSource?.Cancel(); - _cancellationTokenSource = new(); + _cancelHover?.Cancel(); + _cancelHover = new(); - Timer.Spawn(e.HoverDelay, () => + Timer.Spawn(HoverDelay, () => { - _verbSystem.CloseGroupMenu(); - if (_contextMenuView.Menus.Count == 0) { return; @@ -111,7 +110,7 @@ namespace Content.Client.ContextMenu.UI { _contextMenuView.AddChildMenu(filteredEntities, realGlobalPosition, e); } - }, _cancellationTokenSource.Token); + }, _cancelHover.Token); } private void OnKeyBindDownStack(object? sender, (GUIBoundKeyEventArgs, StackContextElement) e) @@ -169,10 +168,23 @@ namespace Content.Client.ContextMenu.UI private void OnMouseEnteredSingle(object? sender, SingleContextElement e) { - _cancellationTokenSource?.Cancel(); + // close other pop-ups after a short delay + _cancelHover?.Cancel(); + _cancelHover = new(); + + Timer.Spawn(HoverDelay, () => + { + if (_contextMenuView.Menus.Count == 0) + { + return; + } + + OnCloseChildMenu(sender, e.ParentMenu?.Depth ?? 0); + + }, _cancelHover.Token); + var entity = e.ContextEntity; - _verbSystem.CloseGroupMenu(); OnCloseChildMenu(sender, e.ParentMenu?.Depth ?? 0); @@ -251,91 +263,114 @@ namespace Content.Client.ContextMenu.UI #endregion #region Model Updates - private void SystemOnToggleContainerVisibility(object? sender, bool args) + private bool HandleOpenContextMenu(in PointerInputCmdHandler.PointerInputCmdArgs args) { - _playerCanSeeThroughContainers = args; - } - - private void SystemOnToggleContextMenu(object? sender, PointerInputCmdHandler.PointerInputCmdArgs args) - { - if (_stateManager.CurrentState is not GameScreenBase) + if (args.State != BoundKeyState.Down) { - return; + return false; } - var playerEntity = _playerManager.LocalPlayer?.ControlledEntity; - if (playerEntity == null) + if (_stateManager.CurrentState is not GameScreenBase) { - return; + return false; + } + + var player = _playerManager.LocalPlayer?.ControlledEntity; + if (player == null) + { + return false; } _mapCoordinates = args.Coordinates.ToMap(_entityManager); - if (!_verbSystem.TryGetContextEntities(playerEntity, _mapCoordinates, out var entities)) - { - return; - } - entities = entities.Where(CanSeeOnContextMenu).ToList(); - if (entities.Count > 0) + if (!_verbSystem.TryGetContextEntities(player, _mapCoordinates, out var entities, ignoreVisibility: _verbSystem.CanSeeAllContext)) + return false; + + // do we need to do visiblity checks? + if (_verbSystem.CanSeeAllContext) { _contextMenuView.AddRootMenu(entities); + return true; } - } - public void HandleMoveEvent(ref MoveEvent ev) - { - if (_contextMenuView.Elements.Count == 0) return; - var entity = ev.Sender; - if (_contextMenuView.Elements.ContainsKey(entity)) + //visibility checks + player.TryGetContainer(out var playerContainer); + foreach (var entity in entities.ToList()) { - if (!entity.Transform.MapPosition.InRange(_mapCoordinates, 1.0f)) + if (!entity.TryGetComponent(out ISpriteComponent? spriteComponent) || + !spriteComponent.Visible || + !CanSeeContainerCheck(entity, playerContainer)) { - _contextMenuView.RemoveEntity(entity); + entities.Remove(entity); } } + + if (entities.Count == 0) + return false; + + _contextMenuView.AddRootMenu(entities); + return true; } + /// + /// Can the player see the entity through any entity containers? + /// + /// + /// This is similar to , except that we do not + /// allow the player to be the "parent" container and we allow for see-through containers (display cases). + /// + private bool CanSeeContainerCheck(IEntity entity, IContainer? playerContainer) + { + // is the player inside this entity? + if (playerContainer?.Owner == entity) + return true; + + entity.TryGetContainer(out var entityContainer); + + // are they in the same container (or none?) + if (playerContainer == entityContainer) + return true; + + // Is the entity in a display case? + if (playerContainer == null && entityContainer!.ShowContents) + return true; + + return false; + } + + /// + /// Check that entities in the context menu are still visible. If not, remove them from the context menu. + /// public void Update() { - if (_contextMenuView.Elements.Count == 0) return; + if (_contextMenuView.Elements.Count == 0) + return; + + var player = _playerManager.LocalPlayer?.ControlledEntity; + + if (player == null) + return; foreach (var entity in _contextMenuView.Elements.Keys.ToList()) { - if (entity.Deleted || !_playerCanSeeThroughContainers && entity.IsInContainer()) + if (entity.Deleted || !_verbSystem.CanSeeAllContext && !player.InRangeUnOccluded(entity)) { _contextMenuView.RemoveEntity(entity); + if (_verbSystem.CurrentTarget == entity.Uid) + _verbSystem.CloseVerbMenu(); } } } #endregion - private bool CanSeeOnContextMenu(IEntity entity) - { - if (!entity.TryGetComponent(out ISpriteComponent? spriteComponent) || !spriteComponent.Visible) - { - return false; - } - - if (entity.GetAllComponents().Any(s => !s.ShowContextMenu(entity))) - { - return false; - } - - return _playerCanSeeThroughContainers || !entity.TryGetContainer(out var container) || container.ShowContents; - } - - private void CloseAllMenus() + public void CloseAllMenus() { _contextMenuView.CloseContextPopups(); - _verbSystem.CloseGroupMenu(); _verbSystem.CloseVerbMenu(); } public void Dispose() { - _verbSystem.ToggleContextMenu -= SystemOnToggleContextMenu; - _verbSystem.ToggleContainerVisibility -= SystemOnToggleContainerVisibility; - _contextMenuView.OnKeyBindDownSingle -= OnKeyBindDownSingle; _contextMenuView.OnMouseEnteredSingle -= OnMouseEnteredSingle; _contextMenuView.OnMouseExitedSingle -= OnMouseExitedSingle; @@ -347,6 +382,8 @@ namespace Content.Client.ContextMenu.UI _contextMenuView.OnExitedTree -= OnExitedTree; _contextMenuView.OnCloseRootMenu -= OnCloseRootMenu; _contextMenuView.OnCloseChildMenu -= OnCloseChildMenu; + + CommandBinds.Unregister(); } } } diff --git a/Content.Client/ContextMenu/UI/ContextMenuView.cs b/Content.Client/ContextMenu/UI/ContextMenuView.cs index d3fe353091..1f25206931 100644 --- a/Content.Client/ContextMenu/UI/ContextMenuView.cs +++ b/Content.Client/ContextMenu/UI/ContextMenuView.cs @@ -1,4 +1,3 @@ - using System; using System.Collections.Generic; using System.Linq; @@ -73,7 +72,7 @@ namespace Content.Client.ContextMenu.UI var entitySpriteStates = GroupEntities(entities); var orderedStates = entitySpriteStates.ToList(); - orderedStates.Sort((x, y) => string.CompareOrdinal(x.First().Prototype!.Name, y.First().Prototype!.Name)); + orderedStates.Sort((x, y) => string.CompareOrdinal(x.First().Prototype?.Name, y.First().Prototype?.Name)); AddToUI(orderedStates); _userInterfaceManager.ModalRoot.AddChild(rootContextMenu); diff --git a/Content.Client/ContextMenu/UI/ContextMenuViewGrouping.cs b/Content.Client/ContextMenu/UI/ContextMenuViewGrouping.cs index 67556dbdc4..8175d18431 100644 --- a/Content.Client/ContextMenu/UI/ContextMenuViewGrouping.cs +++ b/Content.Client/ContextMenu/UI/ContextMenuViewGrouping.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Robust.Client.GameObjects; @@ -105,9 +105,13 @@ namespace Content.Client.ContextMenu.UI public int GetHashCode(IEntity e) { var hash = EqualityComparer.Default.GetHashCode(e.Prototype?.ID!); - foreach (var element in e.GetComponent().AllLayers.Where(obj => obj.Visible).Select(s => s.RsiState.Name)) + + if (e.TryGetComponent(out var sprite)) { - hash ^= EqualityComparer.Default.GetHashCode(element!); + foreach (var element in sprite.AllLayers.Where(obj => obj.Visible).Select(s => s.RsiState.Name)) + { + hash ^= EqualityComparer.Default.GetHashCode(element!); + } } return hash; diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs index 5d58fc47fd..4610587e26 100644 --- a/Content.Client/Examine/ExamineSystem.cs +++ b/Content.Client/Examine/ExamineSystem.cs @@ -1,8 +1,9 @@ -using System.Linq; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Content.Shared.Examine; using Content.Shared.Input; +using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Client.Player; @@ -12,6 +13,7 @@ using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.Input.Binding; using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Players; @@ -38,6 +40,8 @@ namespace Content.Client.Examine { IoCManager.InjectDependencies(this); + SubscribeLocalEvent(AddExamineVerb); + CommandBinds.Builder .Bind(ContentKeyFunctions.ExamineEntity, new PointerInputCmdHandler(HandleExamine)) .Register(); @@ -85,6 +89,18 @@ namespace Content.Client.Examine return true; } + private void AddExamineVerb(GetOtherVerbsEvent args) + { + if (!CanExamine(args.User, args.Target)) + return; + + Verb verb = new(); + verb.Act = () => DoExamine(args.Target) ; + verb.Text = Loc.GetString("examine-verb-name"); + verb.IconTexture = "/Textures/Interface/VerbIcons/examine.svg.192dpi.png"; + args.Verbs.Add(verb); + } + public async void DoExamine(IEntity entity) { // Close any examine tooltip that might already be opened diff --git a/Content.Client/Examine/ExamineVerb.cs b/Content.Client/Examine/ExamineVerb.cs deleted file mode 100644 index da6a9b47a7..0000000000 --- a/Content.Client/Examine/ExamineVerb.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Content.Shared.Verbs; -using Robust.Shared.GameObjects; -using Robust.Shared.Localization; - -namespace Content.Client.Examine -{ - [GlobalVerb] - public class ExamineVerb : GlobalVerb - { - public override bool RequireInteractionRange => false; - - public override bool BlockedByContainers => false; - - public override void GetData(IEntity user, IEntity target, VerbData data) - { - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("examine-verb-name"); - data.IconTexture = "/Textures/Interface/VerbIcons/examine.svg.192dpi.png"; - } - - public override void Activate(IEntity user, IEntity target) - { - EntitySystem.Get().DoExamine(target); - } - } -} diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 3db6583d05..4a81755f24 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -26,6 +26,7 @@ namespace Content.Client.Input common.AddFunction(ContentKeyFunctions.TakeScreenshot); common.AddFunction(ContentKeyFunctions.TakeScreenshotNoUI); common.AddFunction(ContentKeyFunctions.Point); + common.AddFunction(ContentKeyFunctions.OpenContextMenu); var human = contexts.GetContext("human"); human.AddFunction(ContentKeyFunctions.SwapHands); @@ -39,7 +40,6 @@ namespace Content.Client.Input human.AddFunction(ContentKeyFunctions.TryPullObject); human.AddFunction(ContentKeyFunctions.MovePulledObject); human.AddFunction(ContentKeyFunctions.ReleasePulledObject); - human.AddFunction(ContentKeyFunctions.OpenContextMenu); human.AddFunction(ContentKeyFunctions.OpenCraftingMenu); human.AddFunction(ContentKeyFunctions.OpenInventoryMenu); human.AddFunction(ContentKeyFunctions.SmartEquipBackpack); @@ -82,7 +82,6 @@ namespace Content.Client.Input aghost.AddFunction(EngineKeyFunctions.MoveLeft); aghost.AddFunction(EngineKeyFunctions.MoveRight); aghost.AddFunction(EngineKeyFunctions.Walk); - aghost.AddFunction(ContentKeyFunctions.OpenContextMenu); aghost.AddFunction(ContentKeyFunctions.SwapHands); aghost.AddFunction(ContentKeyFunctions.Drop); aghost.AddFunction(ContentKeyFunctions.ThrowItemInHand); @@ -93,7 +92,6 @@ namespace Content.Client.Input ghost.AddFunction(EngineKeyFunctions.MoveLeft); ghost.AddFunction(EngineKeyFunctions.MoveRight); ghost.AddFunction(EngineKeyFunctions.Walk); - ghost.AddFunction(ContentKeyFunctions.OpenContextMenu); common.AddFunction(ContentKeyFunctions.OpenEntitySpawnWindow); common.AddFunction(ContentKeyFunctions.OpenSandboxWindow); diff --git a/Content.Client/Items/Managers/ItemSlotManager.cs b/Content.Client/Items/Managers/ItemSlotManager.cs index 9e0be4bda9..e131cb6b5a 100644 --- a/Content.Client/Items/Managers/ItemSlotManager.cs +++ b/Content.Client/Items/Managers/ItemSlotManager.cs @@ -78,7 +78,7 @@ namespace Content.Client.Items.Managers else if (args.Function == ContentKeyFunctions.OpenContextMenu) { _entitySystemManager.GetEntitySystem() - .OpenContextMenu(item, _uiMgr.ScreenToUIPosition(args.PointerLocation)); + .OpenVerbMenu(item, _uiMgr.ScreenToUIPosition(args.PointerLocation)); } else if (args.Function == ContentKeyFunctions.ActivateItemInWorld) { diff --git a/Content.Client/Pulling/PullableComponent.cs b/Content.Client/Pulling/PullableComponent.cs deleted file mode 100644 index ff39f07cc8..0000000000 --- a/Content.Client/Pulling/PullableComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Pulling.Components; -using Robust.Shared.GameObjects; - -namespace Content.Client.Pulling -{ - [RegisterComponent] - [ComponentReference(typeof(SharedPullableComponent))] - public class PullableComponent : SharedPullableComponent - { - } -} diff --git a/Content.Client/Pulling/PullingSystem.cs b/Content.Client/Pulling/PullingSystem.cs index 04135bb062..91ec1f5cd9 100644 --- a/Content.Client/Pulling/PullingSystem.cs +++ b/Content.Client/Pulling/PullingSystem.cs @@ -1,4 +1,5 @@ -using Content.Shared.Pulling; +using Content.Shared.Pulling; +using Content.Shared.Pulling.Components; using JetBrains.Annotations; using Robust.Client.Physics; @@ -13,8 +14,8 @@ namespace Content.Client.Pulling UpdatesAfter.Add(typeof(PhysicsSystem)); - SubscribeLocalEvent(OnPullableMove); - SubscribeLocalEvent(OnPullableStopMove); + SubscribeLocalEvent(OnPullableMove); + SubscribeLocalEvent(OnPullableStopMove); } } } diff --git a/Content.Client/Rotatable/RotatableComponent.cs b/Content.Client/Rotatable/RotatableComponent.cs deleted file mode 100644 index 5284292c8c..0000000000 --- a/Content.Client/Rotatable/RotatableComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Rotatable; -using Robust.Shared.GameObjects; - -namespace Content.Client.Rotatable -{ - [RegisterComponent] - [ComponentReference(typeof(SharedRotatableComponent))] - public class RotatableComponent : SharedRotatableComponent - { - } -} diff --git a/Content.Client/Verbs/VerbMenuElement.cs b/Content.Client/Verbs/VerbMenuElement.cs new file mode 100644 index 0000000000..b4681e82ac --- /dev/null +++ b/Content.Client/Verbs/VerbMenuElement.cs @@ -0,0 +1,246 @@ +using System.Collections.Generic; +using System.Threading; +using Content.Client.ContextMenu.UI; +using Content.Client.Resources; +using Content.Shared.Verbs; +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.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Utility; +using static Robust.Client.UserInterface.Controls.BoxContainer; +using Timer = Robust.Shared.Timing.Timer; + +namespace Content.Client.Verbs +{ + + /// + /// This pop-up appears when hovering over a verb category in the context menu. + /// + public sealed class VerbCategoryPopup : ContextMenuPopup + { + public VerbCategoryPopup(VerbSystem system, IEnumerable verbs, VerbType type, EntityUid target, bool drawOnlyIcons) + : base() + { + // Do any verbs have icons? If not, don't bother leaving space for icons in the pop-up. + var drawVerbIcons = false; + foreach (var verb in verbs) + { + if (verb.Icon != null) + { + drawVerbIcons = true; + break; + } + } + + // If no verbs have icons. we cannot draw only icons + if (drawVerbIcons == false) + drawOnlyIcons = false; + + // If we are drawing only icons, show them side by side + if (drawOnlyIcons) + List.Orientation = LayoutOrientation.Horizontal; + + foreach (var verb in verbs) + { + AddToMenu(new VerbButton(system, verb, type, target, drawVerbIcons)); + } + } + } + + public sealed class VerbButton : BaseButton + { + public VerbButton(VerbSystem system, Verb verb, VerbType type, EntityUid target, bool drawIcons = true, bool categoryPrefix = false) : base() + { + Disabled = verb.Disabled; + ToolTip = verb.Tooltip; + TooltipDelay = 0.5f; + + var buttonContents = new BoxContainer { Orientation = LayoutOrientation.Horizontal }; + + // maybe draw verb icons + if (drawIcons) + { + TextureRect icon = new() + { + MinSize = (ContextMenuPopup.ButtonHeight, ContextMenuPopup.ButtonHeight), + Stretch = TextureRect.StretchMode.KeepCentered, + TextureScale = (0.5f, 0.5f) + }; + + // Even though we are drawing icons, the icon for this specific verb may be null. + if (verb.Icon != null) + { + icon.Texture = verb.Icon.Frame0(); + } else if (categoryPrefix && verb.Category?.Icon != null) + { + // we will use the category icon instead + icon.Texture = verb.Category.Icon.Frame0(); + } + + buttonContents.AddChild(icon); + } + + // maybe add a label + if (verb.Text != string.Empty || categoryPrefix) + { + // First add a small bit of padding + buttonContents.AddChild(new Control { MinSize = (4, ContextMenuPopup.ButtonHeight) }); + + var label = new RichTextLabel(); + var text = categoryPrefix ? verb.Category!.Text + " " + verb.Text : verb.Text; + label.SetMessage(FormattedMessage.FromMarkupPermissive(text.Trim())); + label.VerticalAlignment = VAlignment.Center; + buttonContents.AddChild(label); + + // Then also add some padding after the text. + buttonContents.AddChild(new Control { MinSize = (4, ContextMenuPopup.ButtonHeight) }); + } + + AddChild(buttonContents); + + if (Disabled) + return; + + // give the button functionality! + OnPressed += _ => + { + if (verb.CloseMenu) + system.ContextMenuPresenter.CloseAllMenus(); + system.TryExecuteVerb(verb, target, type); + }; + } + + protected override void Draw(DrawingHandleScreen handle) + { + base.Draw(handle); + + if (Disabled) + { + // use transparent-black rectangle to create a darker background. + handle.DrawRect(PixelSizeBox, new Color(0,0,0,155)); + } + else if (DrawMode == DrawModeEnum.Hover) + { + // Draw a lighter shade of gray when hovered over + handle.DrawRect(PixelSizeBox, Color.DarkSlateGray); + } + } + } + + public sealed class VerbCategoryButton : Control + { + private readonly VerbSystem _system; + + private CancellationTokenSource? _openCancel; + + /// + /// Whether or not to hide member verb text and just show icons. + /// + /// + /// If no members have icons, this option is ignored and text is shown anyways. Defaults to using . + /// + private readonly bool _drawOnlyIcons; + + /// + /// The pop-up that appears when hovering over this verb group. + /// + private readonly VerbCategoryPopup _popup; + + public VerbCategoryButton(VerbSystem system, VerbCategory category, IEnumerable verbs, VerbType type, EntityUid target, bool? drawOnlyIcons = null) : base() + { + _system = system; + _drawOnlyIcons = drawOnlyIcons ?? category.IconsOnly; + + MouseFilter = MouseFilterMode.Stop; + + // Contents of the button stored in this box container + var box = new BoxContainer() { Orientation = LayoutOrientation.Horizontal }; + + // First we add the icon for the verb group + var icon = new TextureRect + { + MinSize = (ContextMenuPopup.ButtonHeight, ContextMenuPopup.ButtonHeight), + TextureScale = (0.5f, 0.5f), + Stretch = TextureRect.StretchMode.KeepCentered, + }; + if (category.Icon != null) + { + icon.Texture = category.Icon.Frame0(); + } + box.AddChild(icon); + + // Some padding before the text + box.AddChild(new Control { MinSize = (4, ContextMenuPopup.ButtonHeight) }); + + // Then we add the label + var label = new RichTextLabel(); + label.SetMessage(FormattedMessage.FromMarkupPermissive(category.Text)); + label.HorizontalExpand = true; + label.VerticalAlignment = VAlignment.Center; + box.AddChild(label); + + // Then also add some padding after the text. + box.AddChild(new Control { MinSize = (4, ContextMenuPopup.ButtonHeight) }); + + // Then add the little ">" icon that tells you it's a group of verbs + box.AddChild(new TextureRect + { + Texture = IoCManager.Resolve() + .GetTexture("/Textures/Interface/VerbIcons/group.svg.192dpi.png"), + TextureScale = (0.5f, 0.5f), + Stretch = TextureRect.StretchMode.KeepCentered, + }); + + // The pop-up that appears when hovering over the button + _popup = new VerbCategoryPopup(_system, verbs, type, target, _drawOnlyIcons); + UserInterfaceManager.ModalRoot.AddChild(_popup); + + AddChild(box); + } + + protected override void Draw(DrawingHandleScreen handle) + { + base.Draw(handle); + + if (this == UserInterfaceManager.CurrentlyHovered || + _system.CurrentCategoryPopup == _popup) + { + handle.DrawRect(PixelSizeBox, Color.DarkSlateGray); + } + } + + /// + /// Open a verb category pop-up after a short delay. + /// + protected override void MouseEntered() + { + base.MouseEntered(); + + _openCancel = new CancellationTokenSource(); + + Timer.Spawn(ContextMenuPresenter.HoverDelay, () => + { + _system.CurrentCategoryPopup?.Close(); + _system.CurrentCategoryPopup = _popup; + var upperRight = GlobalPosition + (Width + ContextMenuPopup.MarginSize, -ContextMenuPopup.MarginSize); + _popup.Open(UIBox2.FromDimensions(upperRight, (1, 1)), GlobalPosition); + }, _openCancel.Token); + } + + /// + /// Cancel the delayed pop-up + /// + protected override void MouseExited() + { + base.MouseExited(); + + _openCancel?.Cancel(); + _openCancel = null; + } + } +} diff --git a/Content.Client/Verbs/VerbSystem.cs b/Content.Client/Verbs/VerbSystem.cs index bc7ed79af0..eeace00254 100644 --- a/Content.Client/Verbs/VerbSystem.cs +++ b/Content.Client/Verbs/VerbSystem.cs @@ -1,535 +1,228 @@ -using System; using System.Collections.Generic; -using System.Reflection; -using System.Threading; +using System.Linq; using Content.Client.ContextMenu.UI; -using Content.Client.Resources; using Content.Shared.GameTicking; -using Content.Shared.Input; using Content.Shared.Verbs; using JetBrains.Annotations; -using Robust.Client.Graphics; using Robust.Client.Player; -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.Map; using Robust.Shared.Maths; -using Robust.Shared.Utility; -using static Robust.Client.UserInterface.Controls.BoxContainer; -using Timer = Robust.Shared.Timing.Timer; namespace Content.Client.Verbs { [UsedImplicitly] public sealed class VerbSystem : SharedVerbSystem { - [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; - public event EventHandler? ToggleContextMenu; - public event EventHandler? ToggleContainerVisibility; + public ContextMenuPresenter ContextMenuPresenter = default!; - private ContextMenuPresenter _contextMenuPresenter = default!; - private VerbPopup? _currentVerbListRoot; - private VerbPopup? _currentGroupList; - private EntityUid _currentEntity; + public EntityUid CurrentTarget; + public ContextMenuPopup? CurrentVerbPopup; + public ContextMenuPopup? CurrentCategoryPopup; + public Dictionary> CurrentVerbs = new(); - // TODO: Move presenter out of the system - // TODO: Separate the rest of the UI from the logic + /// + /// Whether to show all entities on the context menu. + /// + /// + /// Verb execution will only be affected if the server also agrees that this player can see the target + /// entity. + /// + public bool CanSeeAllContext = false; + + // TODO VERBS Move presenter out of the system + // TODO VERBS Separate the rest of the UI from the logic public override void Initialize() { base.Initialize(); SubscribeNetworkEvent(Reset); - SubscribeNetworkEvent(FillEntityPopup); - SubscribeNetworkEvent(HandleContainerVisibilityMessage); + SubscribeNetworkEvent(HandleVerbResponse); + SubscribeNetworkEvent(SetSeeAllContext); - _contextMenuPresenter = new ContextMenuPresenter(this); - SubscribeLocalEvent(_contextMenuPresenter.HandleMoveEvent); + ContextMenuPresenter = new ContextMenuPresenter(this); + } - CommandBinds.Builder - .Bind(ContentKeyFunctions.OpenContextMenu, - new PointerInputCmdHandler(HandleOpenContextMenu)) - .Register(); + private void Reset(RoundRestartCleanupEvent ev) + { + ContextMenuPresenter.CloseAllMenus(); } public override void Shutdown() { base.Shutdown(); - - _contextMenuPresenter?.Dispose(); - - CommandBinds.Unregister(); - } - - public void Reset(RoundRestartCleanupEvent ev) - { - ToggleContainerVisibility?.Invoke(this, false); - } - - private bool HandleOpenContextMenu(in PointerInputCmdHandler.PointerInputCmdArgs args) - { - if (args.State == BoundKeyState.Down) - { - ToggleContextMenu?.Invoke(this, args); - } - return true; - } - private void HandleContainerVisibilityMessage(PlayerContainerVisibilityMessage ev) - { - ToggleContainerVisibility?.Invoke(this, ev.CanSeeThrough); + ContextMenuPresenter?.Dispose(); } public override void Update(float frameTime) { base.Update(frameTime); - _contextMenuPresenter?.Update(); + ContextMenuPresenter?.Update(); } - public void OpenContextMenu(IEntity entity, ScreenCoordinates screenCoordinates) + private void SetSeeAllContext(SetSeeAllContextEvent args) { - if (_currentVerbListRoot != null) + CanSeeAllContext = args.CanSeeAllContext; + } + + /// + /// Execute actions associated with the given verb. If there are no defined actions, this will instead ask + /// the server to run the given verb. + /// + public void TryExecuteVerb(Verb verb, EntityUid target, VerbType verbType) + { + if (!TryExecuteVerb(verb)) + RaiseNetworkEvent(new TryExecuteVerbEvent(target, verb, verbType)); + } + + public void OpenVerbMenu(IEntity target, ScreenCoordinates screenCoordinates) + { + if (CurrentVerbPopup != null) { CloseVerbMenu(); } - _currentEntity = entity.Uid; - _currentVerbListRoot = new VerbPopup(); - _userInterfaceManager.ModalRoot.AddChild(_currentVerbListRoot); - _currentVerbListRoot.OnPopupHide += CloseVerbMenu; + var user = _playerManager.LocalPlayer?.ControlledEntity; + if (user == null) + return; - if (!entity.Uid.IsClientSide()) + CurrentTarget = target.Uid; + + CurrentVerbPopup = new ContextMenuPopup(); + _userInterfaceManager.ModalRoot.AddChild(CurrentVerbPopup); + CurrentVerbPopup.OnPopupHide += CloseVerbMenu; + + CurrentVerbs = GetVerbs(target, user, VerbType.All); + + if (!target.Uid.IsClientSide()) { - _currentVerbListRoot.List.AddChild(new Label { Text = Loc.GetString("verb-system-waiting-on-server-text") }); - RaiseNetworkEvent(new VerbSystemMessages.RequestVerbsMessage(_currentEntity)); + CurrentVerbPopup.AddToMenu(new Label { Text = Loc.GetString("verb-system-waiting-on-server-text") }); + RaiseNetworkEvent(new RequestServerVerbsEvent(CurrentTarget, VerbType.All)); } + // Show the menu + FillVerbPopup(CurrentVerbPopup); var box = UIBox2.FromDimensions(screenCoordinates.Position, (1, 1)); - _currentVerbListRoot.Open(box); + CurrentVerbPopup.Open(box); } public void OnContextButtonPressed(IEntity entity) { - OpenContextMenu(entity, _userInterfaceManager.MousePositionScaled); + OpenVerbMenu(entity, _userInterfaceManager.MousePositionScaled); } - private void FillEntityPopup(VerbSystemMessages.VerbsResponseMessage msg) + private void HandleVerbResponse(VerbsResponseEvent msg) { - if (_currentEntity != msg.Entity || !EntityManager.TryGetEntity(_currentEntity, out var entity)) + if (CurrentTarget != msg.Entity || CurrentVerbPopup == null) { return; } - DebugTools.AssertNotNull(_currentVerbListRoot); - - var buttons = new Dictionary>(); - var groupIcons = new Dictionary(); - - var vBox = _currentVerbListRoot!.List; - vBox.DisposeAllChildren(); - - // Local variable so that scope capture ensures this is the correct value. - var curEntity = _currentEntity; - - foreach (var data in msg.Verbs) + // This **should** not happen. + if (msg.Verbs == null) { - var list = buttons.GetOrNew(data.Category); - - if (data.CategoryIcon != null && !groupIcons.ContainsKey(data.Category)) - { - groupIcons.Add(data.Category, data.CategoryIcon); - } - - list.Add(new ListedVerbData(data.Text, !data.Available, data.Key, entity.ToString()!, () => - { - RaiseNetworkEvent(new VerbSystemMessages.UseVerbMessage(curEntity, data.Key)); - CloseAllMenus(); - }, data.Icon)); + // update "waiting for server...". + CurrentVerbPopup.List.DisposeAllChildren(); + CurrentVerbPopup.AddToMenu(new Label { Text = Loc.GetString("verb-system-null-server-response") }); + FillVerbPopup(CurrentVerbPopup); + return; } - var user = GetUserEntity(); - //Get verbs, component dependent. - foreach (var (component, verb) in VerbUtility.GetVerbs(entity)) + // Add the new server-side verbs. + foreach (var (verbType, verbSet) in msg.Verbs) { - if (!VerbUtility.VerbAccessChecks(user, entity, verb)) + SortedSet sortedVerbs = new (verbSet); + if (!CurrentVerbs.TryAdd(verbType, sortedVerbs)) { - continue; + CurrentVerbs[verbType].UnionWith(sortedVerbs); } - - var verbData = verb.GetData(user, component); - - if (verbData.IsInvisible) - continue; - - var list = buttons.GetOrNew(verbData.Category); - - if (verbData.CategoryIcon != null && !groupIcons.ContainsKey(verbData.Category)) - { - groupIcons.Add(verbData.Category, verbData.CategoryIcon); - } - - list.Add(new ListedVerbData(verbData.Text, verbData.IsDisabled, verb.ToString()!, entity.ToString()!, - () => verb.Activate(user, component), verbData.Icon)); } - //Get global verbs. Visible for all entities regardless of their components. - foreach (var globalVerb in VerbUtility.GetGlobalVerbs(Assembly.GetExecutingAssembly())) + // Clear currently shown verbs and show new ones + CurrentVerbPopup.List.DisposeAllChildren(); + FillVerbPopup(CurrentVerbPopup); + } + + private void FillVerbPopup(ContextMenuPopup popup) + { + if (CurrentTarget == EntityUid.Invalid) + return; + + // Add verbs to pop-up, grouped by type. Order determined by how types are defined VerbTypes + var types = CurrentVerbs.Keys.ToList(); + types.Sort(); + foreach (var type in types) { - if (!VerbUtility.VerbAccessChecks(user, entity, globalVerb)) - { - continue; - } - - var verbData = globalVerb.GetData(user, entity); - - if (verbData.IsInvisible) - continue; - - var list = buttons.GetOrNew(verbData.Category); - - if (verbData.CategoryIcon != null && !groupIcons.ContainsKey(verbData.Category)) - { - groupIcons.Add(verbData.Category, verbData.CategoryIcon); - } - - list.Add(new ListedVerbData(verbData.Text, verbData.IsDisabled, globalVerb.ToString()!, - entity.ToString()!, - () => globalVerb.Activate(user, entity), verbData.Icon)); + AddVerbSet(popup, type); } - if (buttons.Count > 0) - { - var first = true; - foreach (var (category, verbs) in buttons) - { - if (string.IsNullOrEmpty(category)) - continue; - - if (!first) - { - vBox.AddChild(new PanelContainer - { - MinSize = (0, 2), - PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#333") } - }); - } - - first = false; - - groupIcons.TryGetValue(category, out var icon); - - vBox.AddChild(CreateCategoryButton(category, verbs, icon)); - } - - if (buttons.ContainsKey("")) - { - buttons[""].Sort((a, b) => string.Compare(a.Text, b.Text, StringComparison.CurrentCulture)); - - foreach (var verb in buttons[""]) - { - if (!first) - { - vBox.AddChild(new PanelContainer - { - MinSize = (0, 2), - PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#333") } - }); - } - - first = false; - - vBox.AddChild(CreateVerbButton(verb)); - } - } - } - else + // Were the verb lists empty? + if (popup.List.ChildCount == 0) { var panel = new PanelContainer(); panel.AddChild(new Label { Text = Loc.GetString("verb-system-no-verbs-text") }); - vBox.AddChild(panel); + popup.AddChild(panel); } + + popup.InvalidateMeasure(); } - private VerbButton CreateVerbButton(ListedVerbData data) + /// + /// Add a list of verbs to a BoxContainer. Iterates over the given verbs list and creates GUI buttons. + /// + private void AddVerbSet(ContextMenuPopup popup, VerbType type) { - var button = new VerbButton - { - Text = Loc.GetString(data.Text), - Disabled = data.Disabled - }; + if (!CurrentVerbs.TryGetValue(type, out var verbSet) || verbSet.Count == 0) + return; - if (data.Icon != null) - { - button.Icon = data.Icon.Frame0(); - } + HashSet listedCategories = new(); - if (!data.Disabled) + foreach (var verb in verbSet) { - button.OnPressed += _ => + if (verb.Category == null) { - CloseAllMenus(); - try - { - data.Action.Invoke(); - } - catch (Exception e) - { - Logger.ErrorS("verb", "Exception in verb {0} on {1}:\n{2}", data.VerbName, data.OwnerName, e); - } - }; + // Lone verb without a category. just create a button for it + popup.AddToMenu(new VerbButton(this, verb, type, CurrentTarget)); + continue; + } + + if (listedCategories.Contains(verb.Category.Text)) + { + // This verb was already included in a verb-category button added by a previous verb + continue; + } + + // Get the verbs in the category + var verbsInCategory = verbSet.Where(v => v.Category?.Text == verb.Category.Text); + + popup.AddToMenu( + new VerbCategoryButton(this, verb.Category, verbsInCategory, type, CurrentTarget)); + listedCategories.Add(verb.Category.Text); + continue; + } - - return button; - } - - private Control CreateCategoryButton(string text, List verbButtons, SpriteSpecifier? icon) - { - verbButtons.Sort((a, b) => string.Compare(a.Text, b.Text, StringComparison.CurrentCulture)); - - return new VerbGroupButton(this, verbButtons, icon) - { - Text = Loc.GetString(text), - }; } public void CloseVerbMenu() { - _currentVerbListRoot?.Dispose(); - _currentVerbListRoot = null; - _currentEntity = EntityUid.Invalid; - } - - private void CloseAllMenus() - { - CloseVerbMenu(); - // CloseContextPopups(); - CloseGroupMenu(); - } - - public void CloseGroupMenu() - { - _currentGroupList?.Dispose(); - _currentGroupList = null; - } - - private IEntity GetUserEntity() - { - return _playerManager.LocalPlayer!.ControlledEntity!; - } - - private sealed class VerbPopup : Popup - { - public BoxContainer List { get; } - - public VerbPopup() + if (CurrentVerbPopup != null) { - AddChild(new PanelContainer - { - Children = {(List = new BoxContainer - { - Orientation = LayoutOrientation.Vertical - })}, - PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#111E")} - }); - } - } - - private sealed class VerbButton : BaseButton - { - private readonly Label _label; - private readonly TextureRect _icon; - - public Texture? Icon - { - get => _icon.Texture; - set => _icon.Texture = value; + CurrentVerbPopup.OnPopupHide -= CloseVerbMenu; + CurrentVerbPopup.Dispose(); + CurrentVerbPopup = null; } - public string? Text - { - get => _label.Text; - set => _label.Text = value; - } - - public VerbButton() - { - AddChild(new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - (_icon = new TextureRect - { - MinSize = (32, 32), - Stretch = TextureRect.StretchMode.KeepCentered, - TextureScale = (0.5f, 0.5f) - }), - (_label = new Label()), - // Padding - new Control {MinSize = (8, 0)} - } - }); - } - - protected override void Draw(DrawingHandleScreen handle) - { - base.Draw(handle); - - if (DrawMode == DrawModeEnum.Hover) - { - handle.DrawRect(PixelSizeBox, Color.DarkSlateGray); - } - } - } - - private sealed class VerbGroupButton : Control - { - private static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2); - - private readonly VerbSystem _system; - - private readonly Label _label; - private readonly TextureRect _icon; - - private CancellationTokenSource? _openCancel; - - public List VerbButtons { get; } - - public string? Text - { - get => _label.Text; - set => _label.Text = value; - } - - public Texture? Icon - { - get => _icon.Texture; - set => _icon.Texture = value; - } - - public VerbGroupButton(VerbSystem system, List verbButtons, SpriteSpecifier? icon) - { - _system = system; - VerbButtons = verbButtons; - - MouseFilter = MouseFilterMode.Stop; - - AddChild(new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - (_icon = new TextureRect - { - MinSize = (32, 32), - TextureScale = (0.5f, 0.5f), - Stretch = TextureRect.StretchMode.KeepCentered - }), - - (_label = new Label { HorizontalExpand = true }), - - // Padding - new Control {MinSize = (8, 0)}, - - new TextureRect - { - Texture = IoCManager.Resolve() - .GetTexture("/Textures/Interface/VerbIcons/group.svg.192dpi.png"), - TextureScale = (0.5f, 0.5f), - Stretch = TextureRect.StretchMode.KeepCentered, - } - } - }); - - if (icon != null) - { - _icon.Texture = icon.Frame0(); - } - } - - protected override void Draw(DrawingHandleScreen handle) - { - base.Draw(handle); - - if (this == UserInterfaceManager.CurrentlyHovered) - { - handle.DrawRect(PixelSizeBox, Color.DarkSlateGray); - } - } - - protected override void MouseEntered() - { - base.MouseEntered(); - - _openCancel = new CancellationTokenSource(); - - Timer.Spawn(HoverDelay, () => - { - if (_system._currentGroupList != null) - { - _system.CloseGroupMenu(); - } - - var popup = _system._currentGroupList = new VerbPopup(); - - var first = true; - foreach (var verb in VerbButtons) - { - if (!first) - { - popup.List.AddChild(new PanelContainer - { - MinSize = (0, 2), - PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#333")} - }); - } - - first = false; - - popup.List.AddChild(_system.CreateVerbButton(verb)); - } - - UserInterfaceManager.ModalRoot.AddChild(popup); - popup.Open(UIBox2.FromDimensions(GlobalPosition + (Width, 0), (1, 1)), GlobalPosition); - }, _openCancel.Token); - } - - protected override void MouseExited() - { - base.MouseExited(); - - _openCancel?.Cancel(); - _openCancel = null; - } - } - - private sealed class ListedVerbData - { - public string Text { get; } - public bool Disabled { get; } - public string VerbName { get; } - public string OwnerName { get; } - public SpriteSpecifier? Icon { get; } - public Action Action { get; } - - public ListedVerbData(string text, bool disabled, string verbName, string ownerName, - Action action, SpriteSpecifier? icon) - { - Text = text; - Disabled = disabled; - VerbName = verbName; - OwnerName = ownerName; - Action = action; - Icon = icon; - } + CurrentCategoryPopup?.Dispose(); + CurrentCategoryPopup = null; + CurrentTarget = EntityUid.Invalid; + CurrentVerbs.Clear(); } } } diff --git a/Content.Client/ViewVariables/ViewVariablesVerb.cs b/Content.Client/ViewVariables/ViewVariablesVerb.cs deleted file mode 100644 index 2150dda02e..0000000000 --- a/Content.Client/ViewVariables/ViewVariablesVerb.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Content.Shared.Verbs; -using Robust.Client.Console; -using Robust.Client.ViewVariables; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; - -namespace Content.Client.ViewVariables -{ - /// - /// Global verb that opens a view variables window for the entity in question. - /// - [GlobalVerb] - class ViewVariablesVerb : GlobalVerb - { - public override bool RequireInteractionRange => false; - public override bool BlockedByContainers => false; - - public override void GetData(IEntity user, IEntity target, VerbData data) - { - var groupController = IoCManager.Resolve(); - if (!groupController.CanViewVar()) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = "View Variables"; - data.CategoryData = VerbCategories.Debug; - data.IconTexture = "/Textures/Interface/VerbIcons/vv.svg.192dpi.png"; - } - - public override void Activate(IEntity user, IEntity target) - { - var vvm = IoCManager.Resolve(); - vvm.OpenVV(target); - } - } -} diff --git a/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs b/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs index 0b242319cd..359cd2cfa7 100644 --- a/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs +++ b/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using Content.Server.Damage; +using Content.Server.Administration.Commands; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; using Content.Shared.MobState; @@ -12,7 +12,7 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Commands { [TestFixture] - [TestOf(typeof(RejuvenateVerb))] + [TestOf(typeof(RejuvenateCommand))] public class RejuvenateTest : ContentIntegrationTest { private const string Prototypes = @" @@ -66,7 +66,7 @@ namespace Content.IntegrationTests.Tests.Commands Assert.That(mobState.IsIncapacitated, Is.True); // Rejuvenate them - RejuvenateVerb.PerformRejuvenate(human); + RejuvenateCommand.PerformRejuvenate(human); // Check that it is alive and with no damage Assert.That(mobState.IsAlive, Is.True); diff --git a/Content.Server/Access/Components/IdCardConsoleComponent.cs b/Content.Server/Access/Components/IdCardConsoleComponent.cs index 5ea5085974..db0e0dcf5f 100644 --- a/Content.Server/Access/Components/IdCardConsoleComponent.cs +++ b/Content.Server/Access/Components/IdCardConsoleComponent.cs @@ -1,16 +1,13 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Content.Server.Hands.Components; -using Content.Server.Items; using Content.Server.Power.Components; using Content.Server.UserInterface; using Content.Shared.Access; -using Content.Shared.ActionBlocker; using Content.Shared.Acts; +using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Popups; -using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.GameObjects; @@ -28,21 +25,21 @@ namespace Content.Server.Access.Components { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - private ContainerSlot _privilegedIdContainer = default!; - private ContainerSlot _targetIdContainer = default!; + public ContainerSlot PrivilegedIdContainer = default!; + public ContainerSlot TargetIdContainer = default!; [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(IdCardConsoleUiKey.Key); [ViewVariables] private bool Powered => !Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || receiver.Powered; - private bool PrivilegedIDEmpty => _privilegedIdContainer.ContainedEntities.Count < 1; - private bool TargetIDEmpty => _targetIdContainer.ContainedEntities.Count < 1; + public bool PrivilegedIDEmpty => PrivilegedIdContainer.ContainedEntities.Count < 1; + public bool TargetIDEmpty => TargetIdContainer.ContainedEntities.Count < 1; protected override void Initialize() { base.Initialize(); - _privilegedIdContainer = ContainerHelpers.EnsureContainer(Owner, $"{Name}-privilegedId"); - _targetIdContainer = ContainerHelpers.EnsureContainer(Owner, $"{Name}-targetId"); + PrivilegedIdContainer = ContainerHelpers.EnsureContainer(Owner, $"{Name}-privilegedId"); + TargetIdContainer = ContainerHelpers.EnsureContainer(Owner, $"{Name}-targetId"); Owner.EnsureComponentWarn(); Owner.EnsureComponentWarn(); @@ -68,10 +65,10 @@ namespace Content.Server.Access.Components switch (msg.Button) { case UiButton.PrivilegedId: - HandleId(obj.Session.AttachedEntity, _privilegedIdContainer); + HandleId(obj.Session.AttachedEntity, PrivilegedIdContainer); break; case UiButton.TargetId: - HandleId(obj.Session.AttachedEntity, _targetIdContainer); + HandleId(obj.Session.AttachedEntity, TargetIdContainer); break; } break; @@ -84,7 +81,7 @@ namespace Content.Server.Access.Components } /// - /// Returns true if there is an ID in and said ID satisfies the requirements of . + /// Returns true if there is an ID in and said ID satisfies the requirements of . /// private bool PrivilegedIdIsAuthorized() { @@ -93,22 +90,22 @@ namespace Content.Server.Access.Components return true; } - var privilegedIdEntity = _privilegedIdContainer.ContainedEntity; + var privilegedIdEntity = PrivilegedIdContainer.ContainedEntity; return privilegedIdEntity != null && reader.IsAllowed(privilegedIdEntity); } /// /// Called when the "Submit" button in the UI gets pressed. - /// Writes data passed from the UI into the ID stored in , if present. + /// Writes data passed from the UI into the ID stored in , if present. /// private void TryWriteToTargetId(string newFullName, string newJobTitle, List newAccessList) { - if (!PrivilegedIdIsAuthorized() || _targetIdContainer.ContainedEntity == null) + if (!PrivilegedIdIsAuthorized() || TargetIdContainer.ContainedEntity == null) { return; } - var targetIdEntity = _targetIdContainer.ContainedEntity; + var targetIdEntity = TargetIdContainer.ContainedEntity; var targetIdComponent = targetIdEntity.GetComponent(); targetIdComponent.FullName = newFullName; @@ -128,7 +125,7 @@ namespace Content.Server.Access.Components /// private void HandleId(IEntity user, ContainerSlot container) { - if (!user.TryGetComponent(out IHandsComponent? hands)) + if (!user.TryGetComponent(out SharedHandsComponent? hands)) { Owner.PopupMessage(user, Loc.GetString("access-id-card-console-component-no-hands-error")); return; @@ -144,20 +141,15 @@ namespace Content.Server.Access.Components } } - private void InsertIdFromHand(IEntity user, ContainerSlot container, IHandsComponent hands) + public void InsertIdFromHand(IEntity user, ContainerSlot container, SharedHandsComponent hands) { - var isId = hands.GetActiveHand?.Owner.HasComponent(); - if (isId != true) - { + if (!hands.TryGetActiveHeldEntity(out var heldEntity)) return; - } - if (hands.ActiveHand == null) - { + if (!heldEntity.HasComponent()) return; - } - if (!hands.TryPutHandIntoContainer(hands.ActiveHand, container)) + if (!hands.TryPutHandIntoContainer(hands.ActiveHand!, container)) { Owner.PopupMessage(user, Loc.GetString("access-id-card-console-component-cannot-let-go-error")); return; @@ -165,7 +157,7 @@ namespace Content.Server.Access.Components UpdateUserInterface(); } - private void PutIdInHand(ContainerSlot container, IHandsComponent hands) + public void PutIdInHand(ContainerSlot container, SharedHandsComponent hands) { var idEntity = container.ContainedEntity; if (idEntity == null || !container.Remove(idEntity)) @@ -174,13 +166,13 @@ namespace Content.Server.Access.Components } UpdateUserInterface(); - hands.PutInHand(idEntity.GetComponent()); + hands.TryPutInActiveHandOrAny(idEntity); } private void UpdateUserInterface() { - var isPrivilegedIdPresent = _privilegedIdContainer.ContainedEntity != null; - var targetIdEntity = _targetIdContainer.ContainedEntity; + var isPrivilegedIdPresent = PrivilegedIdContainer.ContainedEntity != null; + var targetIdEntity = TargetIdContainer.ContainedEntity; IdCardConsoleBoundUserInterfaceState newState; // this could be prettier if (targetIdEntity == null) @@ -192,8 +184,8 @@ namespace Content.Server.Access.Components null, null, null, - _privilegedIdContainer.ContainedEntity?.Name ?? string.Empty, - _targetIdContainer.ContainedEntity?.Name ?? string.Empty); + PrivilegedIdContainer.ContainedEntity?.Name ?? string.Empty, + TargetIdContainer.ContainedEntity?.Name ?? string.Empty); } else { @@ -206,8 +198,8 @@ namespace Content.Server.Access.Components targetIdComponent.FullName, targetIdComponent.JobTitle, targetAccessComponent.Tags.ToArray(), - _privilegedIdContainer.ContainedEntity?.Name ?? string.Empty, - _targetIdContainer.ContainedEntity?.Name ?? string.Empty); + PrivilegedIdContainer.ContainedEntity?.Name ?? string.Empty, + TargetIdContainer.ContainedEntity?.Name ?? string.Empty); } UserInterface?.SetState(newState); } @@ -233,89 +225,34 @@ namespace Content.Server.Access.Components return false; } - if (!item.HasComponent() || !user.TryGetComponent(out IHandsComponent? hand)) + if (!item.HasComponent() || !user.TryGetComponent(out SharedHandsComponent? hand)) { return false; } if (PrivilegedIDEmpty) { - InsertIdFromHand(user, _privilegedIdContainer, hand); + InsertIdFromHand(user, PrivilegedIdContainer, hand); } else if (TargetIDEmpty) { - InsertIdFromHand(user, _targetIdContainer, hand); + InsertIdFromHand(user, TargetIdContainer, hand); } UpdateUserInterface(); return true; } - [Verb] - public sealed class EjectPrivilegedIDVerb : Verb - { - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, IdCardConsoleComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("access-eject-privileged-id-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - data.Visibility = component.PrivilegedIDEmpty ? VerbVisibility.Invisible : VerbVisibility.Visible; - } - - protected override void Activate(IEntity user, IdCardConsoleComponent component) - { - if (!user.TryGetComponent(out IHandsComponent? hand)) - { - return; - } - component.PutIdInHand(component._privilegedIdContainer, hand); - } - } - - public sealed class EjectTargetIDVerb : Verb - { - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, IdCardConsoleComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("access-eject-target-id-verb-get-data-text"); - data.Visibility = component.TargetIDEmpty ? VerbVisibility.Invisible : VerbVisibility.Visible; - data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, IdCardConsoleComponent component) - { - if (!user.TryGetComponent(out IHandsComponent? hand)) - { - return; - } - component.PutIdInHand(component._targetIdContainer, hand); - } - } - public void OnBreak(BreakageEventArgs eventArgs) { - var privileged = _privilegedIdContainer.ContainedEntity; + var privileged = PrivilegedIdContainer.ContainedEntity; if (privileged != null) - _privilegedIdContainer.Remove(privileged); + PrivilegedIdContainer.Remove(privileged); - var target = _targetIdContainer.ContainedEntity; + var target = TargetIdContainer.ContainedEntity; if (target != null) - _targetIdContainer.Remove(target); + TargetIdContainer.Remove(target); } } } diff --git a/Content.Server/Access/IdCardConsoleSystem.cs b/Content.Server/Access/IdCardConsoleSystem.cs new file mode 100644 index 0000000000..8dbc2d0972 --- /dev/null +++ b/Content.Server/Access/IdCardConsoleSystem.cs @@ -0,0 +1,80 @@ +using Content.Server.Access.Components; +using Content.Shared.ActionBlocker; +using Content.Shared.Verbs; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; + +namespace Content.Server.Access +{ + public class IdCardConsoleSystem : EntitySystem + { + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(AddInsertVerbs); + SubscribeLocalEvent(AddEjectVerbs); + } + + private void AddInsertVerbs(EntityUid uid, IdCardConsoleComponent component, GetInteractionVerbsEvent args) + { + if (args.Using == null || + !args.CanAccess || + !args.CanInteract || + !args.Using.HasComponent() || + !_actionBlockerSystem.CanDrop(args.User)) + return; + + // Can we insert a privileged ID? + if (component.PrivilegedIDEmpty) + { + Verb verb = new(); + verb.Act = () => component.InsertIdFromHand(args.User, component.PrivilegedIdContainer, args.Hands!); + verb.Category = VerbCategory.Insert; + verb.Text = Loc.GetString("id-card-console-privileged-id"); + args.Verbs.Add(verb); + } + + // Can we insert a target ID? + if (component.TargetIDEmpty) + { + Verb verb = new(); + verb.Act = () => component.InsertIdFromHand(args.User, component.TargetIdContainer, args.Hands!); + verb.Category = VerbCategory.Insert; + verb.Text = Loc.GetString("id-card-console-target-id"); + args.Verbs.Add(verb); + } + } + + private void AddEjectVerbs(EntityUid uid, IdCardConsoleComponent component, GetAlternativeVerbsEvent args) + { + if (args.Hands == null || + !args.CanAccess || + !args.CanInteract || + !_actionBlockerSystem.CanPickup(args.User)) + return; + + // Can we eject a privileged ID? + if (!component.PrivilegedIDEmpty) + { + Verb verb = new(); + verb.Act = () => component.PutIdInHand(component.PrivilegedIdContainer, args.Hands); + verb.Category = VerbCategory.Eject; + verb.Text = Loc.GetString("id-card-console-privileged-id"); + args.Verbs.Add(verb); + } + + // Can we eject a target ID? + if (!component.TargetIDEmpty) + { + Verb verb = new(); + verb.Act = () => component.PutIdInHand(component.TargetIdContainer, args.Hands); + verb.Category = VerbCategory.Eject; + verb.Text = Loc.GetString("id-card-console-target-id"); + args.Verbs.Add(verb); + } + } + } +} diff --git a/Content.Server/Administration/AdminVerbSystem.cs b/Content.Server/Administration/AdminVerbSystem.cs new file mode 100644 index 0000000000..a9873ca246 --- /dev/null +++ b/Content.Server/Administration/AdminVerbSystem.cs @@ -0,0 +1,188 @@ +using Content.Server.Administration.Commands; +using Content.Server.Administration.Managers; +using Content.Server.Administration.UI; +using Content.Server.Configurable; +using Content.Server.Disposal.Tube.Components; +using Content.Server.EUI; +using Content.Server.Ghost.Roles; +using Content.Server.Inventory.Components; +using Content.Server.Mind.Commands; +using Content.Server.Mind.Components; +using Content.Server.Players; +using Content.Server.Verbs; +using Content.Shared.Administration; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Interaction.Helpers; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Server.Console; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; + +namespace Content.Server.Administration +{ + /// + /// System to provide various global admin/debug verbs + /// + public class AdminVerbSystem : EntitySystem + { + [Dependency] private readonly IConGroupController _groupController = default!; + [Dependency] private readonly IAdminManager _adminManager = default!; + [Dependency] private readonly EuiManager _euiManager = default!; + [Dependency] private readonly GhostRoleSystem _ghostRoleSystem = default!; + [Dependency] private readonly VerbSystem _verbSystem = default!; + + public override void Initialize() + { + SubscribeLocalEvent(AddDebugVerbs); + } + + private void AddDebugVerbs(GetOtherVerbsEvent args) + { + if (!args.User.TryGetComponent(out var actor)) + return; + + var player = actor.PlayerSession; + + // Delete verb + if (_groupController.CanCommand(player, "deleteentity")) + { + Verb verb = new(); + verb.Text = Loc.GetString("delete-verb-get-data-text"); + verb.Category = VerbCategory.Debug; + verb.IconTexture = "/Textures/Interface/VerbIcons/delete.svg.192dpi.png"; + verb.Act = () => args.Target.Delete(); + args.Verbs.Add(verb); + } + + // Rejuvenate verb + if (_groupController.CanCommand(player, "rejuvenate")) + { + Verb verb = new(); + verb.Text = Loc.GetString("rejuvenate-verb-get-data-text"); + verb.Category = VerbCategory.Debug; + verb.IconTexture = "/Textures/Interface/VerbIcons/rejuvenate.svg.192dpi.png"; + verb.Act = () => RejuvenateCommand.PerformRejuvenate(args.Target); + args.Verbs.Add(verb); + } + + // Control mob verb + if (_groupController.CanCommand(player, "controlmob") && + args.User != args.Target && + args.User.HasComponent() && + args.Target.TryGetComponent(out var targetMind)) + { + Verb verb = new(); + verb.Text = Loc.GetString("control-mob-verb-get-data-text"); + verb.Category = VerbCategory.Debug; + // TODO VERB ICON control mob icon + verb.Act = () => + { + targetMind.Mind?.TransferTo(null); + player.ContentData()?.Mind?.TransferTo(args.Target, ghostCheckOverride: true); + }; + args.Verbs.Add(verb); + } + + // Make Sentient verb + if (_groupController.CanCommand(player, "makesentient") && + args.User != args.Target && + !args.Target.HasComponent()) + { + Verb verb = new(); + verb.Text = Loc.GetString("make-sentient-verb-get-data-text"); + verb.Category = VerbCategory.Debug; + verb.IconTexture = "/Textures/Interface/VerbIcons/sentient.svg.192dpi.png"; + verb.Act = () => MakeSentientCommand.MakeSentient(args.Target); + args.Verbs.Add(verb); + } + + // Set clothing verb + if (_groupController.CanCommand(player, "setoutfit") && + args.Target.HasComponent()) + { + Verb verb = new(); + verb.Text = Loc.GetString("set-outfit-verb-get-data-text"); + verb.Category = VerbCategory.Debug; + verb.IconTexture = "/Textures/Interface/VerbIcons/outfit.svg.192dpi.png"; + verb.Act = () => _euiManager.OpenEui(new SetOutfitEui(args.Target), player); + args.Verbs.Add(verb); + } + + // In range unoccluded verb + if (_groupController.CanCommand(player, "inrangeunoccluded")) + { + Verb verb = new(); + verb.Text = Loc.GetString("in-range-unoccluded-verb-get-data-text"); + verb.Category = VerbCategory.Debug; + verb.IconTexture = "/Textures/Interface/VerbIcons/information.svg.192dpi.png"; + verb.Act = () => + { + var message = args.User.InRangeUnOccluded(args.Target) + ? Loc.GetString("in-range-unoccluded-verb-on-activate-not-occluded") + : Loc.GetString("in-range-unoccluded-verb-on-activate-occluded"); + args.Target.PopupMessage(args.User, message); + }; + args.Verbs.Add(verb); + } + + // Get Disposal tube direction verb + if (_groupController.CanCommand(player, "tubeconnections") && + args.Target.TryGetComponent(out var tube)) + { + Verb verb = new(); + verb.Text = Loc.GetString("tube-direction-verb-get-data-text"); + verb.Category = VerbCategory.Debug; + verb.IconTexture = "/Textures/Interface/VerbIcons/information.svg.192dpi.png"; + verb.Act = () => tube.PopupDirections(args.User); + args.Verbs.Add(verb); + } + + // Make ghost role verb + if (_groupController.CanCommand(player, "makeghostrole") && + !(args.Target.GetComponentOrNull()?.HasMind ?? false)) + { + Verb verb = new(); + verb.Text = Loc.GetString("make-ghost-role-verb-get-data-text"); + verb.Category = VerbCategory.Debug; + // TODO VERB ICON add ghost icon + // Where is the national park service icon for haunted forests? + verb.Act = () => _ghostRoleSystem.OpenMakeGhostRoleEui(player, args.Target.Uid); + args.Verbs.Add(verb); + } + + // Configuration verb. Is this even used for anything!? + if (_groupController.CanAdminMenu(player) && + args.Target.TryGetComponent(out var config)) + { + Verb verb = new(); + verb.Text = Loc.GetString("configure-verb-get-data-text"); + verb.IconTexture = "/Textures/Interface/VerbIcons/settings.svg.192dpi.png"; + verb.Category = VerbCategory.Debug; + verb.Act = () => config.OpenUserInterface(actor); + args.Verbs.Add(verb); + } + + // Add reagent verb + if (_adminManager.HasAdminFlag(player, AdminFlags.Fun) && + args.Target.HasComponent()) + { + Verb verb = new(); + verb.Text = Loc.GetString("admin-add-reagent-verb-get-data-text"); + verb.Category = VerbCategory.Debug; + verb.IconTexture = "/Textures/Interface/VerbIcons/spill.svg.192dpi.png"; + verb.Act = () => _euiManager.OpenEui(new AdminAddReagentEui(args.Target), player); + + // TODO CHEMISTRY + // Add reagent ui broke after solution refactor. Needs fixing + verb.Disabled = true; + verb.Tooltip = "Currently non functional after solution refactor."; + verb.Priority = -2; + + args.Verbs.Add(verb); + } + } + } +} diff --git a/Content.Server/Administration/Commands/Rejuvenate.cs b/Content.Server/Administration/Commands/RejuvenateCommand.cs similarity index 53% rename from Content.Server/Administration/Commands/Rejuvenate.cs rename to Content.Server/Administration/Commands/RejuvenateCommand.cs index 6ddee77ce1..12616f563c 100644 --- a/Content.Server/Administration/Commands/Rejuvenate.cs +++ b/Content.Server/Administration/Commands/RejuvenateCommand.cs @@ -1,5 +1,12 @@ -using Content.Server.Damage; +using Content.Server.Atmos.Components; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Nutrition.Components; +using Content.Server.Nutrition.EntitySystems; +using Content.Server.Stunnable.Components; using Content.Shared.Administration; +using Content.Shared.Damage; +using Content.Shared.MobState; +using Content.Shared.Nutrition.Components; using Robust.Server.Player; using Robust.Shared.Console; using Robust.Shared.GameObjects; @@ -9,7 +16,7 @@ using Robust.Shared.Localization; namespace Content.Server.Administration.Commands { [AdminCommand(AdminFlags.Admin)] - class Rejuvenate : IConsoleCommand + public class RejuvenateCommand : IConsoleCommand { public string Command => "rejuvenate"; @@ -28,7 +35,7 @@ namespace Content.Server.Administration.Commands shell.WriteLine(Loc.GetString("rejuvenate-command-no-entity-attached-message")); return; } - RejuvenateVerb.PerformRejuvenate(player.AttachedEntity); + PerformRejuvenate(player.AttachedEntity); } var entityManager = IoCManager.Resolve(); @@ -39,7 +46,27 @@ namespace Content.Server.Administration.Commands shell.WriteLine(Loc.GetString("shell-could-not-find-entity",("entity", arg))); continue; } - RejuvenateVerb.PerformRejuvenate(entity); + PerformRejuvenate(entity); + } + } + + public static void PerformRejuvenate(IEntity target) + { + target.GetComponentOrNull()?.UpdateState(0); + target.GetComponentOrNull()?.ResetFood(); + target.GetComponentOrNull()?.ResetThirst(); + target.GetComponentOrNull()?.ResetStuns(); + + EntitySystem.Get().Extinguish(target.Uid); + + if (target.TryGetComponent(out DamageableComponent? damageable)) + { + EntitySystem.Get().SetAllDamage(damageable, 0); + } + + if (target.TryGetComponent(out CreamPiedComponent? creamPied)) + { + EntitySystem.Get().SetCreamPied(target.Uid, creamPied, false); } } } diff --git a/Content.Server/Administration/UI/AdminAddReagentEui.cs b/Content.Server/Administration/UI/AdminAddReagentEui.cs new file mode 100644 index 0000000000..0f675cb5cc --- /dev/null +++ b/Content.Server/Administration/UI/AdminAddReagentEui.cs @@ -0,0 +1,94 @@ +using Content.Server.Administration.Managers; +using Content.Server.EUI; +using Content.Shared.Administration; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Eui; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; + +namespace Content.Server.Administration.UI +{ + public sealed class AdminAddReagentEui : BaseEui + { + private readonly IEntity _target; + [Dependency] private readonly IAdminManager _adminManager = default!; + + public AdminAddReagentEui(IEntity target) + { + _target = target; + + IoCManager.InjectDependencies(this); + } + + public override void Opened() + { + StateDirty(); + } + + public override EuiStateBase GetNewState() + { + if (EntitySystem.Get() + .TryGetSolution(_target, "default", out var container)) + { + return new AdminAddReagentEuiState + { + CurVolume = container.CurrentVolume, + MaxVolume = container.MaxVolume + }; + } + + return new AdminAddReagentEuiState + { + CurVolume = ReagentUnit.Zero, + MaxVolume = ReagentUnit.Zero + }; + } + + public override void HandleMessage(EuiMessageBase msg) + { + switch (msg) + { + case AdminAddReagentEuiMsg.Close: + Close(); + break; + case AdminAddReagentEuiMsg.DoAdd doAdd: + // Double check that user wasn't de-adminned in the mean time... + // Or the target was deleted. + if (!_adminManager.HasAdminFlag(Player, AdminFlags.Fun) || _target.Deleted) + { + Close(); + return; + } + + var id = doAdd.ReagentId; + var amount = doAdd.Amount; + var solutionsSys = EntitySystem.Get(); + + if (_target.TryGetComponent(out InjectableSolutionComponent? injectable) + && solutionsSys.TryGetSolution(_target, injectable.Name, out var targetSolution)) + { + var solution = new Solution(id, amount); + solutionsSys.Inject(_target.Uid, targetSolution, solution); + } + else + { + //TODO decide how to find the solution + if (solutionsSys.TryGetSolution(_target, "default", out var solution)) + { + solutionsSys.TryAddReagent(_target.Uid,solution, id, amount, out _); + } + } + + StateDirty(); + + if (doAdd.CloseAfter) + Close(); + + break; + } + } + } +} diff --git a/Content.Server/Administration/Verbs/AdminAddReagentVerb.cs b/Content.Server/Administration/Verbs/AdminAddReagentVerb.cs deleted file mode 100644 index 936ef2a5d8..0000000000 --- a/Content.Server/Administration/Verbs/AdminAddReagentVerb.cs +++ /dev/null @@ -1,150 +0,0 @@ -using Content.Server.Administration.Managers; -using Content.Server.EUI; -using Content.Shared.Administration; -using Content.Shared.Chemistry.Components; -using Content.Shared.Chemistry.Components.SolutionManager; -using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Chemistry.Reagent; -using Content.Shared.Eui; -using Content.Shared.Verbs; -using Robust.Server.GameObjects; -using Robust.Server.Player; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; - -namespace Content.Server.Administration.Verbs -{ - [GlobalVerb] - internal sealed class AdminAddReagentVerb : GlobalVerb - { - public override bool RequireInteractionRange => false; - public override bool BlockedByContainers => false; - - private const AdminFlags ReqFlags = AdminFlags.Fun; - - private static void OpenAddReagentMenu(IPlayerSession player, IEntity target) - { - var euiMgr = IoCManager.Resolve(); - euiMgr.OpenEui(new AdminAddReagentEui(target), player); - } - - public override void GetData(IEntity user, IEntity target, VerbData data) - { - // ISolutionInteractionsComponent doesn't exactly have an interface for "admin tries to refill this", so... - // Still have a path for SolutionContainerComponent in case it doesn't allow direct refilling. - if (!(target.HasComponent() - && target.HasComponent())) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("admin-add-reagent-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/spill.svg.192dpi.png"; - data.CategoryData = VerbCategories.Debug; - data.Visibility = VerbVisibility.Invisible; - - var adminManager = IoCManager.Resolve(); - - if (user.TryGetComponent(out var player)) - { - if (adminManager.HasAdminFlag(player.PlayerSession, ReqFlags)) - { - data.Visibility = VerbVisibility.Visible; - } - } - } - - public override void Activate(IEntity user, IEntity target) - { - var groupController = IoCManager.Resolve(); - if (user.TryGetComponent(out var player)) - { - if (groupController.HasAdminFlag(player.PlayerSession, ReqFlags)) - OpenAddReagentMenu(player.PlayerSession, target); - } - } - - private sealed class AdminAddReagentEui : BaseEui - { - private readonly IEntity _target; - [Dependency] private readonly IAdminManager _adminManager = default!; - - public AdminAddReagentEui(IEntity target) - { - _target = target; - - IoCManager.InjectDependencies(this); - } - - public override void Opened() - { - StateDirty(); - } - - public override EuiStateBase GetNewState() - { - if (EntitySystem.Get() - .TryGetSolution(_target, "default", out var container)) - { - return new AdminAddReagentEuiState - { - CurVolume = container.CurrentVolume, - MaxVolume = container.MaxVolume - }; - } - - return new AdminAddReagentEuiState - { - CurVolume = ReagentUnit.Zero, - MaxVolume = ReagentUnit.Zero - }; - } - - public override void HandleMessage(EuiMessageBase msg) - { - switch (msg) - { - case AdminAddReagentEuiMsg.Close: - Close(); - break; - case AdminAddReagentEuiMsg.DoAdd doAdd: - // Double check that user wasn't de-adminned in the mean time... - // Or the target was deleted. - if (!_adminManager.HasAdminFlag(Player, ReqFlags) || _target.Deleted) - { - Close(); - return; - } - - var id = doAdd.ReagentId; - var amount = doAdd.Amount; - var solutionsSys = EntitySystem.Get(); - - if (_target.TryGetComponent(out InjectableSolutionComponent? injectable) - && solutionsSys.TryGetSolution(_target, injectable.Name, out var targetSolution)) - { - var solution = new Solution(id, amount); - solutionsSys.Inject(_target.Uid, targetSolution, solution); - } - else - { - //TODO decide how to find the solution - if (solutionsSys.TryGetSolution(_target, "default", out var solution)) - { - solutionsSys.TryAddReagent(_target.Uid,solution, id, amount, out _); - } - } - - StateDirty(); - - if (doAdd.CloseAfter) - Close(); - - break; - } - } - } - } -} diff --git a/Content.Server/Administration/Verbs/DeleteVerb.cs b/Content.Server/Administration/Verbs/DeleteVerb.cs deleted file mode 100644 index 262347fdda..0000000000 --- a/Content.Server/Administration/Verbs/DeleteVerb.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Content.Shared.Verbs; -using Robust.Server.Console; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; - -namespace Content.Server.Administration.Verbs -{ - [GlobalVerb] - public class DeleteVerb : GlobalVerb - { - public override bool RequireInteractionRange => false; - public override bool BlockedByContainers => false; - - public override void GetData(IEntity user, IEntity target, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - var groupController = IoCManager.Resolve(); - - if (!user.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - if (!groupController.CanCommand(actor.PlayerSession, "deleteentity")) - { - return; - } - - data.Text = Loc.GetString("delete-verb-get-data-text"); - data.CategoryData = VerbCategories.Debug; - data.Visibility = VerbVisibility.Visible; - data.IconTexture = "/Textures/Interface/VerbIcons/delete.svg.192dpi.png"; - } - - public override void Activate(IEntity user, IEntity target) - { - var groupController = IoCManager.Resolve(); - - if (!user.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - if (!groupController.CanCommand(actor.PlayerSession, "deleteentity")) - { - return; - } - - target.Delete(); - } - } -} diff --git a/Content.Server/Alert/Click/StopBeingPulled.cs b/Content.Server/Alert/Click/StopBeingPulled.cs index 2b70e7e028..50d591cd30 100644 --- a/Content.Server/Alert/Click/StopBeingPulled.cs +++ b/Content.Server/Alert/Click/StopBeingPulled.cs @@ -16,10 +16,9 @@ namespace Content.Server.Alert.Click { public void AlertClicked(ClickAlertEventArgs args) { - var ps = EntitySystem.Get(); if (args.Player.TryGetComponent(out var playerPullable)) { - ps.TryStopPull(playerPullable); + EntitySystem.Get().TryStopPull(playerPullable); } } } diff --git a/Content.Server/Alert/Click/StopPulling.cs b/Content.Server/Alert/Click/StopPulling.cs index 37c68a742a..1e098733b4 100644 --- a/Content.Server/Alert/Click/StopPulling.cs +++ b/Content.Server/Alert/Click/StopPulling.cs @@ -1,4 +1,4 @@ -using Content.Shared.Alert; +using Content.Shared.Alert; using Content.Shared.Pulling; using Content.Shared.Pulling.Components; using JetBrains.Annotations; diff --git a/Content.Server/Atmos/Components/GasTankComponent.cs b/Content.Server/Atmos/Components/GasTankComponent.cs index fcd1c0d46c..e0f0c6780d 100644 --- a/Content.Server/Atmos/Components/GasTankComponent.cs +++ b/Content.Server/Atmos/Components/GasTankComponent.cs @@ -16,7 +16,6 @@ using Content.Shared.DragDrop; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Sound; -using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Server.Player; @@ -320,37 +319,6 @@ namespace Content.Server.Atmos.Components { DisconnectFromInternals(eventArgs.User); } - - /// - /// Open interaction window - /// - [Verb] - private sealed class ControlVerb : Verb - { - public override bool RequireInteractionRange => true; - - protected override void GetData(IEntity user, GasTankComponent component, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - if (!user.HasComponent()) - { - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("control-verb-open-control-panel-text"); - } - - protected override void Activate(IEntity user, GasTankComponent component) - { - if (!user.TryGetComponent(out var actor)) - { - return; - } - - component.OpenInterface(actor.PlayerSession); - } - } } [UsedImplicitly] diff --git a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs index 7c8cb0bfc8..3080367c8a 100644 --- a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs @@ -1,7 +1,10 @@ using Content.Server.Atmos.Components; +using Content.Shared.Verbs; using JetBrains.Annotations; +using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Localization; namespace Content.Server.Atmos.EntitySystems { @@ -13,6 +16,24 @@ namespace Content.Server.Atmos.EntitySystems private const float TimerDelay = 0.5f; private float _timer = 0f; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(AddOpenUIVerb); + } + + private void AddOpenUIVerb(EntityUid uid, GasTankComponent component, GetActivationVerbsEvent args) + { + if (!args.CanAccess || !args.User.TryGetComponent(out var actor)) + return; + + Verb verb = new(); + verb.Act = () => component.OpenInterface(actor.PlayerSession); + verb.Text = Loc.GetString("control-verb-open-control-panel-text"); + // TODO VERBS add "open UI" icon? + args.Verbs.Add(verb); + } + public override void Update(float frameTime) { base.Update(frameTime); diff --git a/Content.Server/Body/Commands/AttachBodyPartCommand.cs b/Content.Server/Body/Commands/AttachBodyPartCommand.cs index ba7e35c9d8..494c4bcfc2 100644 --- a/Content.Server/Body/Commands/AttachBodyPartCommand.cs +++ b/Content.Server/Body/Commands/AttachBodyPartCommand.cs @@ -99,7 +99,7 @@ namespace Content.Server.Body.Commands return; } - body.SetPart($"{nameof(AttachBodyPartVerb)}-{partEntity.Uid}", part); + body.SetPart($"AttachBodyPartVerb-{partEntity.Uid}", part); } } } diff --git a/Content.Server/Body/Part/BodyPartComponent.cs b/Content.Server/Body/Part/BodyPartComponent.cs index 63b09f2138..942bcc70e9 100644 --- a/Content.Server/Body/Part/BodyPartComponent.cs +++ b/Content.Server/Body/Part/BodyPartComponent.cs @@ -218,54 +218,5 @@ namespace Content.Server.Body.Part break; } } - - [Verb] - public class AttachBodyPartVerb : Verb - { - protected override void GetData(IEntity user, BodyPartComponent component, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - if (user == component.Owner) - { - return; - } - - if (!user.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - var groupController = IoCManager.Resolve(); - - if (!groupController.CanCommand(actor.PlayerSession, "attachbodypart")) - { - return; - } - - if (!user.TryGetComponent(out SharedBodyComponent? body)) - { - return; - } - - if (body.HasPart(component)) - { - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("attach-bodypart-verb-get-data-text"); - } - - protected override void Activate(IEntity user, BodyPartComponent component) - { - if (!user.TryGetComponent(out SharedBodyComponent? body)) - { - return; - } - - body.SetPart($"{nameof(AttachBodyPartVerb)}-{component.Owner.Uid}", component); - } - } } } diff --git a/Content.Server/Buckle/Components/BuckleComponent.cs b/Content.Server/Buckle/Components/BuckleComponent.cs index fe41940f2d..fc30eb1892 100644 --- a/Content.Server/Buckle/Components/BuckleComponent.cs +++ b/Content.Server/Buckle/Components/BuckleComponent.cs @@ -10,8 +10,9 @@ using Content.Shared.Buckle.Components; using Content.Shared.Interaction.Helpers; using Content.Shared.MobState.Components; using Content.Shared.Popups; +using Content.Shared.Pulling; +using Content.Shared.Pulling.Components; using Content.Shared.Standing; -using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Containers; @@ -150,7 +151,7 @@ namespace Content.Server.Buckle.Components } } - private bool CanBuckle(IEntity? user, IEntity to, [NotNullWhen(true)] out StrapComponent? strap) + public bool CanBuckle(IEntity? user, IEntity to, [NotNullWhen(true)] out StrapComponent? strap) { strap = null; @@ -264,7 +265,7 @@ namespace Content.Server.Buckle.Components SendMessage(new BuckleMessage(Owner, to)); - if (Owner.TryGetComponent(out PullableComponent? ownerPullable)) + if (Owner.TryGetComponent(out SharedPullableComponent? ownerPullable)) { if (ownerPullable.Puller != null) { @@ -272,7 +273,7 @@ namespace Content.Server.Buckle.Components } } - if (to.TryGetComponent(out PullableComponent? toPullable)) + if (to.TryGetComponent(out SharedPullableComponent? toPullable)) { if (toPullable.Puller == Owner) { @@ -427,29 +428,5 @@ namespace Content.Server.Buckle.Components IsOnStrapEntityThisFrame = false; } - - /// - /// Allows the unbuckling of the owning entity through a verb if - /// anyone right clicks them. - /// - [Verb] - private sealed class BuckleVerb : Verb - { - protected override void GetData(IEntity user, BuckleComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user) || !component.Buckled) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("buckle-verb-unbuckle"); - } - - protected override void Activate(IEntity user, BuckleComponent component) - { - component.TryUnbuckle(user); - } - } } } diff --git a/Content.Server/Buckle/Components/StrapComponent.cs b/Content.Server/Buckle/Components/StrapComponent.cs index cf7622ea67..a94f1d6f28 100644 --- a/Content.Server/Buckle/Components/StrapComponent.cs +++ b/Content.Server/Buckle/Components/StrapComponent.cs @@ -6,13 +6,9 @@ using Content.Shared.Alert; using Content.Shared.Buckle.Components; using Content.Shared.DragDrop; using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; -using Content.Shared.Interaction.Helpers; using Content.Shared.Sound; -using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; -using Robust.Shared.Localization; using Robust.Shared.Players; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; @@ -173,53 +169,6 @@ namespace Content.Server.Buckle.Components return buckle.ToggleBuckle(eventArgs.User, Owner); } - [Verb] - private sealed class StrapVerb : Verb - { - protected override void GetData(IEntity user, StrapComponent component, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - if (!EntitySystem.Get().CanInteract(component.Owner) || - !user.TryGetComponent(out var buckle) || - buckle.BuckledTo != null && buckle.BuckledTo != component || - user == component.Owner) - { - return; - } - - var parent = component.Owner.Transform.Parent; - while (parent != null) - { - if (parent == user.Transform) - { - return; - } - - parent = parent.Parent; - } - - if (!user.InRangeUnobstructed(component, buckle.Range)) - { - return; - } - - data.Visibility = VerbVisibility.Visible; - data.IconTexture = buckle.BuckledTo == null ? "/Textures/Interface/VerbIcons/buckle.svg.192dpi.png" : "/Textures/Interface/VerbIcons/unbuckle.svg.192dpi.png"; - data.Text = Loc.GetString(buckle.BuckledTo == null ? "strap-verb-get-data-text-buckle" : "strap-verb-get-data-text-unbuckle"); - } - - protected override void Activate(IEntity user, StrapComponent component) - { - if (!user.TryGetComponent(out var buckle)) - { - return; - } - - buckle.ToggleBuckle(user, component.Owner); - } - } - public override bool DragDropOn(DragDropEvent eventArgs) { if (!eventArgs.Dragged.TryGetComponent(out BuckleComponent? buckleComponent)) return false; diff --git a/Content.Server/Buckle/BuckleSystem.cs b/Content.Server/Buckle/Systems/BuckleSystem.cs similarity index 81% rename from Content.Server/Buckle/BuckleSystem.cs rename to Content.Server/Buckle/Systems/BuckleSystem.cs index a9a32a4d7a..7e2ccac8ee 100644 --- a/Content.Server/Buckle/BuckleSystem.cs +++ b/Content.Server/Buckle/Systems/BuckleSystem.cs @@ -2,12 +2,14 @@ using Content.Server.Buckle.Components; using Content.Server.Interaction; using Content.Shared.Buckle; using Content.Shared.Interaction; +using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.GameObjects; +using Robust.Shared.Localization; -namespace Content.Server.Buckle +namespace Content.Server.Buckle.Systems { [UsedImplicitly] internal sealed class BuckleSystem : SharedBuckleSystem @@ -30,6 +32,27 @@ namespace Content.Server.Buckle SubscribeLocalEvent(ContainerModifiedStrap); SubscribeLocalEvent(HandleInteractHand); + + SubscribeLocalEvent(AddUnbuckleVerb); + } + + private void AddUnbuckleVerb(EntityUid uid, BuckleComponent component, GetInteractionVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || !component.Buckled) + return; + + Verb verb = new(); + verb.Act = () => component.TryUnbuckle(args.User); + verb.Category = VerbCategory.Unbuckle; + + if (args.Target == args.User && args.Using == null) + { + // A user is left clicking themselves with an empty hand, while buckled. + // It is very likely they are trying to unbuckle themselves. + verb.Priority = 1; + } + + args.Verbs.Add(verb); } private void HandleInteractHand(EntityUid uid, BuckleComponent component, InteractHandEvent args) diff --git a/Content.Server/Buckle/Systems/StrapSystem.cs b/Content.Server/Buckle/Systems/StrapSystem.cs new file mode 100644 index 0000000000..1a4206ccf6 --- /dev/null +++ b/Content.Server/Buckle/Systems/StrapSystem.cs @@ -0,0 +1,101 @@ +using Content.Server.Buckle.Components; +using Content.Server.Interaction; +using Content.Shared.Hands.Components; +using Content.Shared.Interaction.Helpers; +using Content.Shared.Verbs; +using JetBrains.Annotations; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using System.Collections.Generic; + +namespace Content.Server.Buckle.Systems +{ + [UsedImplicitly] + internal sealed class StrapSystem : EntitySystem + { + [Dependency] InteractionSystem _interactionSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(AddStrapVerbs); + } + + // TODO ECS BUCKLE/STRAP These 'Strap' verbs are an incestuous mess of buckle component and strap component + // functions. Whenever these are fully ECSed, maybe do it in a way that allows for these verbs to be handled in + // a sensible manner in a single system? + + private void AddStrapVerbs(EntityUid uid, StrapComponent component, GetInteractionVerbsEvent args) + { + if (args.Hands == null || !args.CanAccess || !args.CanInteract) + return; + + // Note that for whatever bloody reason, buckle component has its own interaction range. Additionally, this + // range can be set per-component, so we have to check a modified InRangeUnobstructed for every verb. + + // Add unstrap verbs for every strapped entity. + foreach (var entity in component.BuckledEntities) + { + var buckledComp = entity.GetComponent(); + + if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target, range: buckledComp.Range)) + continue; + + Verb verb = new(); + verb.Act = () => buckledComp.TryUnbuckle(args.User); + verb.Category = VerbCategory.Unbuckle; + if (entity == args.User) + verb.Text = Loc.GetString("verb-self-target-pronoun"); + else + verb.Text = entity.Name; + + // In the event that you have more than once entity with the same name strapped to the same object, + // these two verbs will be identical according to Verb.CompareTo, and only one with actually be added to + // the verb list. However this should rarely ever be a problem. If it ever is, it could be fixed by + // appending an integer to verb.Text to distinguish the verbs. + + args.Verbs.Add(verb); + } + + // Add a verb to buckle the user. + if (args.User.TryGetComponent(out var buckle) && + buckle.BuckledTo != component && + args.User != component.Owner && + component.HasSpace(buckle) && + _interactionSystem.InRangeUnobstructed(args.User, args.Target, range: buckle.Range)) + { + Verb verb = new(); + verb.Act = () => buckle.TryBuckle(args.User, args.Target); + verb.Category = VerbCategory.Buckle; + verb.Text = Loc.GetString("verb-self-target-pronoun"); + args.Verbs.Add(verb); + } + + // If the user is currently holding/pulling an entity that can be buckled, add a verb for that. + if (args.Using != null && + args.Using.TryGetComponent(out var usingBuckle) && + component.HasSpace(usingBuckle) && + _interactionSystem.InRangeUnobstructed(args.Using, args.Target, range: usingBuckle.Range)) + { + // Check that the entity is unobstructed from the target (ignoring the user). + bool Ignored(IEntity entity) => entity == args.User || entity == args.Target || entity == args.Using; + if (!_interactionSystem.InRangeUnobstructed(args.Using, args.Target, usingBuckle.Range, predicate: Ignored)) + return; + + Verb verb = new(); + verb.Act = () => usingBuckle.TryBuckle(args.User, args.Target); + verb.Category = VerbCategory.Buckle; + verb.Text = args.Using.Name; + + // If the used entity is a person being pulled, prioritize this verb. Conversely, if it is + // just a held object, the user is probably just trying to sit down. + verb.Priority = args.Using.HasComponent() ? 1 : -1; + + args.Verbs.Add(verb); + } + } + } +} diff --git a/Content.Server/Cabinet/ItemCabinetComponent.cs b/Content.Server/Cabinet/ItemCabinetComponent.cs index 3490dcbca0..fca1c34c50 100644 --- a/Content.Server/Cabinet/ItemCabinetComponent.cs +++ b/Content.Server/Cabinet/ItemCabinetComponent.cs @@ -1,11 +1,7 @@ -using Content.Shared.ActionBlocker; -using Content.Shared.Interaction.Events; using Content.Shared.Sound; -using Content.Shared.Verbs; using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -using Robust.Shared.Localization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -50,52 +46,5 @@ namespace Content.Server.Cabinet [ViewVariables] [DataField("opened")] public bool Opened { get; set; } = false; - - [Verb] - public sealed class EjectItemFromCabinetVerb : Verb - { - protected override void GetData(IEntity user, ItemCabinetComponent component, VerbData data) - { - if (component.ItemContainer.ContainedEntity == null || !component.Opened || !EntitySystem.Get().CanInteract(user)) - data.Visibility = VerbVisibility.Invisible; - else - { - data.Text = Loc.GetString("comp-item-cabinet-eject-verb-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - data.Visibility = VerbVisibility.Visible; - } - } - - protected override void Activate(IEntity user, ItemCabinetComponent component) - { - component.Owner.EntityManager.EventBus.RaiseLocalEvent(component.Owner.Uid, new TryEjectItemCabinetEvent(user), false); - } - } - - [Verb] - public sealed class ToggleItemCabinetVerb : Verb - { - // Unlike lockers, you cannot open/close cabinets by clicking on them, as this usually removes their item - // instead. So open/close is the alt-interact verb - - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, ItemCabinetComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - data.Visibility = VerbVisibility.Invisible; - else - { - data.Text = Loc.GetString(component.Opened ? "comp-item-cabinet-close-verb-text" : "comp-item-cabinet-open-verb-text"); - data.IconTexture = component.Opened ? "/Textures/Interface/VerbIcons/close.svg.192dpi.png" : "/Textures/Interface/VerbIcons/open.svg.192dpi.png"; - data.Visibility = VerbVisibility.Visible; - } - } - - protected override void Activate(IEntity user, ItemCabinetComponent component) - { - component.Owner.EntityManager.EventBus.RaiseLocalEvent(component.Owner.Uid, new ToggleItemCabinetEvent(), false); - } - } } } diff --git a/Content.Server/Cabinet/ItemCabinetSystem.cs b/Content.Server/Cabinet/ItemCabinetSystem.cs index bc7b031e4d..47471633f1 100644 --- a/Content.Server/Cabinet/ItemCabinetSystem.cs +++ b/Content.Server/Cabinet/ItemCabinetSystem.cs @@ -1,12 +1,16 @@ using Content.Server.Hands.Components; using Content.Server.Items; +using Content.Shared.ActionBlocker; using Content.Shared.Audio; using Content.Shared.Cabinet; +using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Popups; +using Content.Shared.Verbs; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Player; @@ -14,6 +18,8 @@ namespace Content.Server.Cabinet { public class ItemCabinetSystem : EntitySystem { + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + public override void Initialize() { base.Initialize(); @@ -27,6 +33,70 @@ namespace Content.Server.Cabinet SubscribeLocalEvent(OnTryEjectItemCabinet); SubscribeLocalEvent(OnTryInsertItemCabinet); SubscribeLocalEvent(OnToggleItemCabinet); + + SubscribeLocalEvent(AddEjectInsertVerbs); + SubscribeLocalEvent(AddToggleOpenVerb); + } + + private void AddToggleOpenVerb(EntityUid uid, ItemCabinetComponent component, GetActivationVerbsEvent args) + { + if (args.Hands == null || !args.CanAccess || !args.CanInteract) + return; + + // Toggle open verb + Verb toggleVerb = new(); + toggleVerb.Act = () => OnToggleItemCabinet(uid, component); + if (component.Opened) + { + toggleVerb.Text = Loc.GetString("verb-categories-close"); + toggleVerb.IconTexture = "/Textures/Interface/VerbIcons/close.svg.192dpi.png"; + } + else + { + toggleVerb.Text = Loc.GetString("verb-categories-open"); + toggleVerb.IconTexture = "/Textures/Interface/VerbIcons/open.svg.192dpi.png"; + } + args.Verbs.Add(toggleVerb); + } + + private void AddEjectInsertVerbs(EntityUid uid, ItemCabinetComponent component, GetInteractionVerbsEvent args) + { + if (args.Hands == null || !args.CanAccess || !args.CanInteract) + return; + + // "Eject" item verb + if (component.Opened && + component.ItemContainer.ContainedEntity != null && + _actionBlockerSystem.CanPickup(args.User)) + { + Verb verb = new(); + verb.Act = () => + { + TakeItem(component, args.Hands, component.ItemContainer.ContainedEntity, args.User); + UpdateVisuals(component); + }; + verb.Text = Loc.GetString("pick-up-verb-get-data-text"); + verb.IconTexture = "/Textures/Interface/VerbIcons/pickup.svg.192dpi.png"; + args.Verbs.Add(verb); + } + + // Insert item verb + if (component.Opened && + args.Using != null && + _actionBlockerSystem.CanDrop(args.User) && + (component.Whitelist?.IsValid(args.Using) ?? true) && + component.ItemContainer.CanInsert(args.Using)) + { + Verb verb = new(); + verb.Act = () => + { + args.Hands.TryPutEntityIntoContainer(args.Using, component.ItemContainer); + UpdateVisuals(component); + }; + verb.Category = VerbCategory.Insert; + verb.Text = args.Using.Name; + args.Verbs.Add(verb); + } } private void OnMapInitialize(EntityUid uid, ItemCabinetComponent comp, MapInitEvent args) @@ -83,7 +153,7 @@ namespace Content.Server.Cabinet /// /// Toggles the ItemCabinet's state. /// - private void OnToggleItemCabinet(EntityUid uid, ItemCabinetComponent comp, ToggleItemCabinetEvent args) + private void OnToggleItemCabinet(EntityUid uid, ItemCabinetComponent comp, ToggleItemCabinetEvent? args = null) { comp.Opened = !comp.Opened; ClickLatchSound(comp); @@ -115,17 +185,10 @@ namespace Content.Server.Cabinet { if (comp.ItemContainer.ContainedEntity == null || args.Cancelled) return; - if (args.User.TryGetComponent(out HandsComponent? hands)) + if (args.User.TryGetComponent(out SharedHandsComponent? hands)) { - - if (comp.ItemContainer.ContainedEntity.TryGetComponent(out var item)) - { - comp.Owner.PopupMessage(args.User, - Loc.GetString("comp-item-cabinet-successfully-taken", - ("item", comp.ItemContainer.ContainedEntity), - ("cabinet", comp.Owner))); - hands.PutInHandOrDrop(item); - } + // Put into hands + TakeItem(comp, hands, comp.ItemContainer.ContainedEntity, args.User); } else if (comp.ItemContainer.Remove(comp.ItemContainer.ContainedEntity)) { @@ -134,6 +197,24 @@ namespace Content.Server.Cabinet UpdateVisuals(comp); } + /// + /// Tries to eject the ItemCabinet's item, either into the user's hands. Used by both and the eject verbs. + /// + private static void TakeItem(ItemCabinetComponent comp, SharedHandsComponent hands, IEntity containedEntity, IEntity user) + { + if (containedEntity.HasComponent()) + { + if (!hands.TryPutInActiveHandOrAny(containedEntity)) + containedEntity.Transform.Coordinates = hands.Owner.Transform.Coordinates; + + comp.Owner.PopupMessage(user, + Loc.GetString("comp-item-cabinet-successfully-taken", + ("item", containedEntity), + ("cabinet", comp.Owner))); + } + } + private static void UpdateVisuals(ItemCabinetComponent comp) { if (comp.Owner.TryGetComponent(out SharedAppearanceComponent? appearance)) diff --git a/Content.Server/Chemistry/Components/ChemMasterComponent.cs b/Content.Server/Chemistry/Components/ChemMasterComponent.cs index 5dd67f76d3..8ae20e0ec1 100644 --- a/Content.Server/Chemistry/Components/ChemMasterComponent.cs +++ b/Content.Server/Chemistry/Components/ChemMasterComponent.cs @@ -14,7 +14,6 @@ using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Random.Helpers; using Content.Shared.Sound; -using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Containers; @@ -38,10 +37,10 @@ namespace Content.Server.Chemistry.Components public class ChemMasterComponent : SharedChemMasterComponent, IActivate, IInteractUsing { [ViewVariables] - private ContainerSlot _beakerContainer = default!; + public ContainerSlot BeakerContainer = default!; [ViewVariables] - private bool HasBeaker => _beakerContainer.ContainedEntity != null; + public bool HasBeaker => BeakerContainer.ContainedEntity != null; [ViewVariables] private bool _bufferModeTransfer = true; @@ -74,7 +73,7 @@ namespace Content.Server.Chemistry.Components UserInterface.OnReceiveMessage += OnUiReceiveMessage; } - _beakerContainer = + BeakerContainer = ContainerHelpers.EnsureContainer(Owner, $"{Name}-reagentContainerContainer"); _bufferSolution = EntitySystem.Get().EnsureSolution(Owner, SolutionName); @@ -177,7 +176,7 @@ namespace Content.Server.Chemistry.Components /// Returns a private ChemMasterBoundUserInterfaceState GetUserInterfaceState() { - var beaker = _beakerContainer.ContainedEntity; + var beaker = BeakerContainer.ContainedEntity; EntitySystem.Get().TryGetSolution(beaker, SolutionName, out var beakerSolution); // TODO this is just a guess if (beaker == null || beakerSolution == null) @@ -204,17 +203,17 @@ namespace Content.Server.Chemistry.Components /// If this component contains an entity with a , eject it. /// Tries to eject into user's hands first, then ejects onto chem master if both hands are full. /// - private void TryEject(IEntity user) + public void TryEject(IEntity user) { if (!HasBeaker) return; - var beaker = _beakerContainer.ContainedEntity; + var beaker = BeakerContainer.ContainedEntity; if (beaker is null) return; - _beakerContainer.Remove(beaker); + BeakerContainer.Remove(beaker); UpdateUserInterface(); if (!user.TryGetComponent(out var hands) || @@ -227,7 +226,7 @@ namespace Content.Server.Chemistry.Components private void TransferReagent(string id, ReagentUnit amount, bool isBuffer) { if (!HasBeaker && _bufferModeTransfer) return; - var beaker = _beakerContainer.ContainedEntity; + var beaker = BeakerContainer.ContainedEntity; if (beaker is null) return; @@ -428,7 +427,7 @@ namespace Content.Server.Chemistry.Components } else { - _beakerContainer.Insert(activeHandEntity); + BeakerContainer.Insert(activeHandEntity); UpdateUserInterface(); } } @@ -445,29 +444,5 @@ namespace Content.Server.Chemistry.Components { SoundSystem.Play(Filter.Pvs(Owner), _clickSound.GetSound(), Owner, AudioParams.Default.WithVolume(-2f)); } - - [Verb] - public sealed class EjectBeakerVerb : Verb - { - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, ChemMasterComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("eject-beaker-verb-get-data-text"); - data.Visibility = component.HasBeaker ? VerbVisibility.Visible : VerbVisibility.Invisible; - data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, ChemMasterComponent component) - { - component.TryEject(user); - } - } } } diff --git a/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs b/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs index ccb037c4a8..9d338e7132 100644 --- a/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs +++ b/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs @@ -15,7 +15,6 @@ using Content.Shared.Chemistry.Reagent; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Sound; -using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -46,13 +45,13 @@ namespace Content.Server.Chemistry.Components [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [ViewVariables] private ContainerSlot _beakerContainer = default!; + [ViewVariables] public ContainerSlot BeakerContainer = default!; [ViewVariables] [DataField("pack")] private string _packPrototypeId = ""; [DataField("clickSound")] private SoundSpecifier _clickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); - [ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null; + [ViewVariables] public bool HasBeaker => BeakerContainer.ContainedEntity != null; [ViewVariables] private ReagentUnit _dispenseAmount = ReagentUnit.New(10); [UsedImplicitly] @@ -84,7 +83,7 @@ namespace Content.Server.Chemistry.Components UserInterface.OnReceiveMessage += OnUiReceiveMessage; } - _beakerContainer = + BeakerContainer = ContainerHelpers.EnsureContainer(Owner, $"{Name}-reagentContainerContainer"); InitializeFromPrototype(); @@ -228,7 +227,7 @@ namespace Content.Server.Chemistry.Components /// Returns a private ReagentDispenserBoundUserInterfaceState GetUserInterfaceState() { - var beaker = _beakerContainer.ContainedEntity; + var beaker = BeakerContainer.ContainedEntity; if (beaker == null || !beaker.TryGetComponent(out FitsInDispenserComponent? fits) || !EntitySystem.Get().TryGetSolution(beaker, fits.Solution, out var solution)) { @@ -252,16 +251,16 @@ namespace Content.Server.Chemistry.Components /// If this component contains an entity with a , eject it. /// Tries to eject into user's hands first, then ejects onto dispenser if both hands are full. /// - private void TryEject(IEntity user) + public void TryEject(IEntity user) { if (!HasBeaker) return; - var beaker = _beakerContainer.ContainedEntity; + var beaker = BeakerContainer.ContainedEntity; if (beaker is null) return; - _beakerContainer.Remove(beaker); + BeakerContainer.Remove(beaker); UpdateUserInterface(); if (!user.TryGetComponent(out var hands) || @@ -276,12 +275,12 @@ namespace Content.Server.Chemistry.Components /// private void TryClear() { - if (!HasBeaker || !_beakerContainer.ContainedEntity!.TryGetComponent(out FitsInDispenserComponent? fits) || + if (!HasBeaker || !BeakerContainer.ContainedEntity!.TryGetComponent(out FitsInDispenserComponent? fits) || !EntitySystem.Get() - .TryGetSolution(_beakerContainer.ContainedEntity, fits.Solution, out var solution)) + .TryGetSolution(BeakerContainer.ContainedEntity, fits.Solution, out var solution)) return; - EntitySystem.Get().RemoveAllSolution(_beakerContainer.ContainedEntity!.Uid, solution); + EntitySystem.Get().RemoveAllSolution(BeakerContainer.ContainedEntity!.Uid, solution); UpdateUserInterface(); } @@ -294,12 +293,12 @@ namespace Content.Server.Chemistry.Components { if (!HasBeaker) return; - if (_beakerContainer.ContainedEntity is not {} contained || !contained.TryGetComponent(out FitsInDispenserComponent? fits) + if (BeakerContainer.ContainedEntity is not {} contained || !contained.TryGetComponent(out FitsInDispenserComponent? fits) || !EntitySystem.Get() - .TryGetSolution(_beakerContainer.ContainedEntity, fits.Solution, out var solution)) return; + .TryGetSolution(BeakerContainer.ContainedEntity, fits.Solution, out var solution)) return; EntitySystem.Get() - .TryAddReagent(_beakerContainer.ContainedEntity.Uid, solution, Inventory[dispenseIndex].ID, _dispenseAmount, out _); + .TryAddReagent(BeakerContainer.ContainedEntity.Uid, solution, Inventory[dispenseIndex].ID, _dispenseAmount, out _); UpdateUserInterface(); } @@ -360,7 +359,7 @@ namespace Content.Server.Chemistry.Components return false; } - _beakerContainer.Insert(activeHandEntity); + BeakerContainer.Insert(activeHandEntity); UpdateUserInterface(); return true; @@ -378,30 +377,6 @@ namespace Content.Server.Chemistry.Components SoundSystem.Play(Filter.Pvs(Owner), _clickSound.GetSound(), Owner, AudioParams.Default.WithVolume(-2f)); } - [Verb] - public sealed class EjectBeakerVerb : Verb - { - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, ReagentDispenserComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("eject-beaker-verb-get-data-text"); - data.Visibility = component.HasBeaker ? VerbVisibility.Visible : VerbVisibility.Invisible; - data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, ReagentDispenserComponent component) - { - component.TryEject(user); - } - } - private class ReagentInventoryComparer : Comparer { public override int Compare(ReagentDispenserInventoryEntry x, ReagentDispenserInventoryEntry y) diff --git a/Content.Server/Chemistry/Components/SolutionTransferComponent.cs b/Content.Server/Chemistry/Components/SolutionTransferComponent.cs index 52fef40479..1bbb255227 100644 --- a/Content.Server/Chemistry/Components/SolutionTransferComponent.cs +++ b/Content.Server/Chemistry/Components/SolutionTransferComponent.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using Content.Server.UserInterface; -using Content.Shared.ActionBlocker; using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components.SolutionManager; @@ -10,7 +9,6 @@ using Content.Shared.Chemistry.Reagent; using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Content.Shared.Popups; -using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Localization; @@ -53,21 +51,6 @@ namespace Content.Server.Chemistry.Components [ViewVariables(VVAccess.ReadWrite)] public ReagentUnit MaximumTransferAmount { get; set; } = ReagentUnit.New(50); - /// - /// Subjectively, which transfer amount would be best for most activities given the maximum - /// transfer amount. - /// - public ReagentUnit SubjectiveBestTransferAmount() => - MaximumTransferAmount.Int() switch - { - <= 5 => ReagentUnit.New(1), - (> 5) and (<= 25) => ReagentUnit.New(5), - (> 25) and (<= 50) => ReagentUnit.New(10), - (> 50) and (<= 100) => ReagentUnit.New(20), - (> 100) and (<= 500) => ReagentUnit.New(50), - (> 500) => ReagentUnit.New(100) - }; - /// /// Can this entity take reagent from reagent tanks? /// @@ -89,7 +72,7 @@ namespace Content.Server.Chemistry.Components [ViewVariables(VVAccess.ReadWrite)] public bool CanChangeTransferAmount { get; set; } = false; - [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(TransferAmountUiKey.Key); + [ViewVariables] public BoundUserInterface? UserInterface => Owner.GetUIOrNull(TransferAmountUiKey.Key); protected override void Initialize() { @@ -209,119 +192,5 @@ namespace Content.Server.Chemistry.Components return actualAmount; } - - // TODO refactor when dynamic verbs are a thing - - [Verb] - public sealed class MinimumTransferVerb : Verb - { - protected override void GetData(IEntity user, SolutionTransferComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user) || !component.CanChangeTransferAmount) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("comp-solution-transfer-verb-transfer-amount-min", - ("amount", component.MinimumTransferAmount.Int())); - data.CategoryData = VerbCategories.SetTransferAmount; - } - - protected override void Activate(IEntity user, SolutionTransferComponent component) - { - component.TransferAmount = component.MinimumTransferAmount; - user.PopupMessage(Loc.GetString("comp-solution-transfer-set-amount", - ("amount", component.TransferAmount.Int()))); - } - } - - [Verb] - public sealed class DefaultTransferVerb : Verb - { - protected override void GetData(IEntity user, SolutionTransferComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user) || !component.CanChangeTransferAmount) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - var amt = component.SubjectiveBestTransferAmount(); - if (amt > component.MinimumTransferAmount && amt < component.MaximumTransferAmount) - { - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("comp-solution-transfer-verb-transfer-amount-ideal", - ("amount", amt.Int())); - data.CategoryData = VerbCategories.SetTransferAmount; - } - else - { - data.Visibility = VerbVisibility.Invisible; - } - } - - protected override void Activate(IEntity user, SolutionTransferComponent component) - { - component.TransferAmount = component.SubjectiveBestTransferAmount(); - user.PopupMessage(Loc.GetString("comp-solution-transfer-set-amount", - ("amount", component.TransferAmount.Int()))); - } - } - - [Verb] - public sealed class MaximumTransferVerb : Verb - { - protected override void GetData(IEntity user, SolutionTransferComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user) || !component.CanChangeTransferAmount) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("comp-solution-transfer-verb-transfer-amount-max", - ("amount", component.MaximumTransferAmount)); - data.CategoryData = VerbCategories.SetTransferAmount; - } - - protected override void Activate(IEntity user, SolutionTransferComponent component) - { - component.TransferAmount = component.MaximumTransferAmount; - user.PopupMessage(Loc.GetString("comp-solution-transfer-set-amount", - ("amount", component.TransferAmount.Int()))); - } - } - - [Verb] - public sealed class CustomTransferVerb : Verb - { - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, SolutionTransferComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user) || !component.CanChangeTransferAmount) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("comp-solution-transfer-verb-transfer-amount-custom"); - data.CategoryData = VerbCategories.SetTransferAmount; - } - - protected override void Activate(IEntity user, SolutionTransferComponent component) - { - if (!user.TryGetComponent(out var actor)) - { - return; - } - - component.UserInterface?.Open(actor.PlayerSession); - } - } } } diff --git a/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs b/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs index 40f8f35753..50fc5c250d 100644 --- a/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs @@ -1,18 +1,69 @@ -using Content.Server.Chemistry.Components; +using Content.Shared.Verbs; +using Content.Server.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Content.Shared.Chemistry.Components.SolutionManager; +using Robust.Shared.IoC; +using Content.Shared.ActionBlocker; namespace Content.Server.Chemistry.EntitySystems { [UsedImplicitly] public class ChemMasterSystem : EntitySystem { + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnSolutionChange); + SubscribeLocalEvent(AddInsertVerb); + SubscribeLocalEvent(AddEjectVerb); + } + + // TODO VERBS EJECTABLES Standardize eject/insert verbs into a single system? Maybe using something like the + // system mentioned in #4538? The code here is basically identical to the stuff in ChemDispenserSystem + private void AddEjectVerb(EntityUid uid, ChemMasterComponent component, GetAlternativeVerbsEvent args) + { + if (args.Hands == null || + !args.CanAccess || + !args.CanInteract || + !component.HasBeaker || + !_actionBlockerSystem.CanPickup(args.User)) + return; + + Verb verb = new(); + verb.Act = () => + { + component.TryEject(args.User); + component.UpdateUserInterface(); + }; + verb.Category = VerbCategory.Eject; + verb.Text = component.BeakerContainer.ContainedEntity!.Name; + args.Verbs.Add(verb); + } + + private void AddInsertVerb(EntityUid uid, ChemMasterComponent component, GetInteractionVerbsEvent args) + { + if (args.Using == null || + !args.CanAccess || + !args.CanInteract || + component.HasBeaker || + !args.Using.HasComponent() || + !_actionBlockerSystem.CanDrop(args.User)) + return; + + Verb verb = new(); + verb.Act = () => + { + component.BeakerContainer.Insert(args.Using); + component.UpdateUserInterface(); + }; + verb.Category = VerbCategory.Insert; + verb.Text = args.Using.Name; + args.Verbs.Add(verb); } private void OnSolutionChange(EntityUid uid, ChemMasterComponent component, diff --git a/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs b/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs index bcec570e1d..9a90dfaf0c 100644 --- a/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs @@ -1,20 +1,81 @@ -using Content.Server.Chemistry.Components; +using Content.Shared.Verbs; +using Content.Server.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.ActionBlocker; namespace Content.Server.Chemistry.EntitySystems { [UsedImplicitly] public class ReagentDispenserSystem : EntitySystem { + [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnSolutionChange); + + SubscribeLocalEvent(AddEjectVerb); + SubscribeLocalEvent(AddInsertVerb); } + // TODO VERBS EJECTABLES Standardize eject/insert verbs into a single system? Maybe using something like the + // system mentioned in #4538? The code here is basically identical to the stuff in ChemDispenserSystem. + private void AddEjectVerb(EntityUid uid, ReagentDispenserComponent component, GetAlternativeVerbsEvent args) + { + if (args.Hands == null || + !args.CanAccess || + !args.CanInteract || + !component.HasBeaker || + !_actionBlockerSystem.CanPickup(args.User)) + return; + + Verb verb = new(); + verb.Act = () => + { + component.TryEject(args.User); + component.UpdateUserInterface(); + }; + verb.Category = VerbCategory.Eject; + verb.Text = component.BeakerContainer.ContainedEntity!.Name; + + args.Verbs.Add(verb); + } + + private void AddInsertVerb(EntityUid uid, ReagentDispenserComponent component, GetInteractionVerbsEvent args) + { + if (args.Using == null || + !args.CanAccess || + !args.CanInteract || + component.HasBeaker || + !args.Using.HasComponent() || + !_actionBlockerSystem.CanDrop(args.User)) + return; + + if (!args.Using.HasComponent() || + !_solutionContainerSystem.TryGetSolution(args.Using.Uid, "beaker", out _)) + { + return; + } + + Verb verb = new(); + verb.Act = () => + { + component.BeakerContainer.Insert(args.Using); + component.UpdateUserInterface(); + }; + verb.Category = VerbCategory.Insert; + verb.Text = args.Using.Name; + args.Verbs.Add(verb); + } + + private void OnSolutionChange(EntityUid uid, ReagentDispenserComponent component, SolutionChangedEvent args) { component.UpdateUserInterface(); diff --git a/Content.Server/Chemistry/EntitySystems/SolutionTransferSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionTransferSystem.cs new file mode 100644 index 0000000000..315b297d34 --- /dev/null +++ b/Content.Server/Chemistry/EntitySystems/SolutionTransferSystem.cs @@ -0,0 +1,68 @@ +using Content.Shared.Verbs; +using Content.Server.Chemistry.Components; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; +using Robust.Server.GameObjects; +using System.Collections.Generic; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Popups; + +namespace Content.Server.Chemistry.EntitySystems +{ + [UsedImplicitly] + public class SolutionTransferSystem : EntitySystem + { + /// + /// Default transfer amounts for the set-transfer verb. + /// + public static readonly List DefaultTransferAmounts = new() { 1, 5, 10, 25, 50, 100, 250, 500, 1000}; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(AddSetTransferVerbs); + } + + private void AddSetTransferVerbs(EntityUid uid, SolutionTransferComponent component, GetAlternativeVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || !component.CanChangeTransferAmount) + return; + + if (!args.User.TryGetComponent(out var actor)) + return; + + // Custom transfer verb + Verb custom = new(); + custom.Text = Loc.GetString("comp-solution-transfer-verb-custom-amount"); + custom.Category = VerbCategory.SetTransferAmount; + custom.Act = () => component.UserInterface?.Open(actor.PlayerSession); + custom.Priority = 1; + args.Verbs.Add(custom); + + // Add specific transfer verbs according to the container's size + var priority = 0; + foreach (var amount in DefaultTransferAmounts) + { + if ( amount < component.MinimumTransferAmount.Int() || amount > component.MaximumTransferAmount.Int()) + continue; + + Verb verb = new(); + verb.Text = Loc.GetString("comp-solution-transfer-verb-amount", ("amount", amount)); + verb.Category = VerbCategory.SetTransferAmount; + verb.Act = () => + { + component.TransferAmount = ReagentUnit.New(amount); + args.User.PopupMessage(Loc.GetString("comp-solution-transfer-set-amount", ("amount", amount))); + }; + + // we want to sort by size, not alphabetically by the verb text. + verb.Priority = priority; + priority--; + + args.Verbs.Add(verb); + } + } + } +} diff --git a/Content.Server/Climbing/ClimbSystem.cs b/Content.Server/Climbing/ClimbSystem.cs index f10268b15c..e36de443c3 100644 --- a/Content.Server/Climbing/ClimbSystem.cs +++ b/Content.Server/Climbing/ClimbSystem.cs @@ -1,10 +1,14 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Content.Server.Climbing.Components; +using Content.Shared.ActionBlocker; using Content.Shared.Climbing; using Content.Shared.GameTicking; +using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; namespace Content.Server.Climbing { @@ -13,11 +17,32 @@ namespace Content.Server.Climbing { private readonly HashSet _activeClimbers = new(); + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + public override void Initialize() { base.Initialize(); SubscribeLocalEvent(Reset); + SubscribeLocalEvent(AddClimbVerb); + } + + private void AddClimbVerb(EntityUid uid, ClimbableComponent component, GetAlternativeVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || !_actionBlockerSystem.CanMove(args.User)) + return; + + // Check that the user climb. + if (!args.User.TryGetComponent(out ClimbingComponent? climbingComponent) || + climbingComponent.IsClimbing) + return; + + // Add a climb verb + Verb verb = new(); + verb.Act = () => component.TryClimb(args.User); + verb.Text = Loc.GetString("comp-climbable-verb-climb"); + // TODO VERBS ICON add a climbing icon? + args.Verbs.Add(verb); } public void AddActiveClimber(ClimbingComponent climbingComponent) diff --git a/Content.Server/Climbing/Components/ClimbableComponent.cs b/Content.Server/Climbing/Components/ClimbableComponent.cs index 90f218f7b1..2891b38c76 100644 --- a/Content.Server/Climbing/Components/ClimbableComponent.cs +++ b/Content.Server/Climbing/Components/ClimbableComponent.cs @@ -190,7 +190,7 @@ namespace Content.Server.Climbing.Components } } - private async void TryClimb(IEntity user) + public async void TryClimb(IEntity user) { if (!user.TryGetComponent(out ClimbingComponent? climbingComponent) || climbingComponent.IsClimbing) return; @@ -234,29 +234,5 @@ namespace Content.Server.Climbing.Components user.PopupMessage(selfMessage); } } - - /// - /// Allows you to vault an object with the ClimbableComponent through right click - /// - [Verb] - private sealed class ClimbVerb : Verb - { - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, ClimbableComponent component, VerbData data) - { - if (!component.CanVault(user, component.Owner, out var _)) - { - data.Visibility = VerbVisibility.Invisible; - } - - data.Text = Loc.GetString("comp-climbable-verb-climb"); - } - - protected override void Activate(IEntity user, ClimbableComponent component) - { - component.TryClimb(user); - } - } } } diff --git a/Content.Server/Clothing/Components/MagbootsComponent.cs b/Content.Server/Clothing/Components/MagbootsComponent.cs index decec5873e..1360b6fa41 100644 --- a/Content.Server/Clothing/Components/MagbootsComponent.cs +++ b/Content.Server/Clothing/Components/MagbootsComponent.cs @@ -118,26 +118,6 @@ namespace Content.Server.Clothing.Components { return new MagbootsComponentState(On); } - - [UsedImplicitly] - public sealed class ToggleMagbootsVerb : Verb - { - protected override void GetData(IEntity user, MagbootsComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("toggle-magboots-verb-get-data-text"); - } - - protected override void Activate(IEntity user, MagbootsComponent component) - { - component.Toggle(user); - } - } } [UsedImplicitly] diff --git a/Content.Server/Clothing/MagbootsSystem.cs b/Content.Server/Clothing/MagbootsSystem.cs new file mode 100644 index 0000000000..cc005a29b1 --- /dev/null +++ b/Content.Server/Clothing/MagbootsSystem.cs @@ -0,0 +1,29 @@ +using Content.Server.Clothing.Components; +using Content.Shared.Verbs; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; + +namespace Content.Server.Clothing +{ + public sealed class MagbootsSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(AddToggleVerb); + } + + private void AddToggleVerb(EntityUid uid, MagbootsComponent component, GetActivationVerbsEvent args) + { + if (args.User == null || !args.CanAccess || !args.CanInteract) + return; + + Verb verb = new(); + verb.Text = Loc.GetString("toggle-magboots-verb-get-data-text"); + verb.Act = () => component.On = !component.On; + // TODO VERB ICON add toggle icon? maybe a computer on/off symbol? + args.Verbs.Add(verb); + } + } +} diff --git a/Content.Server/Configurable/ConfigurationComponent.cs b/Content.Server/Configurable/ConfigurationComponent.cs index 0aeafeeabf..808d75e5ce 100644 --- a/Content.Server/Configurable/ConfigurationComponent.cs +++ b/Content.Server/Configurable/ConfigurationComponent.cs @@ -7,13 +7,8 @@ using Content.Server.UserInterface; using Content.Shared.Configurable; using Content.Shared.Interaction; using Content.Shared.Tool; -using Content.Shared.Verbs; -using Robust.Server.Console; using Robust.Server.GameObjects; -using Robust.Server.Player; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -117,7 +112,7 @@ namespace Content.Server.Configurable UserInterface?.SetState(new ConfigurationBoundUserInterfaceState(_config)); } - private void OpenUserInterface(ActorComponent actor) + public void OpenUserInterface(ActorComponent actor) { UpdateUserInterface(); UserInterface?.Open(actor.PlayerSession); @@ -130,31 +125,5 @@ namespace Content.Server.Configurable configuration.Add(list[index], value); } } - - [Verb] - public sealed class ConfigureVerb : Verb - { - protected override void GetData(IEntity user, ConfigurationComponent component, VerbData data) - { - var session = user.PlayerSession(); - var groupController = IoCManager.Resolve(); - if (session == null || !groupController.CanAdminMenu(session)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("configure-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/settings.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, ConfigurationComponent component) - { - if (user.TryGetComponent(out ActorComponent? actor)) - { - component.OpenUserInterface(actor); - } - } - } } } diff --git a/Content.Server/Construction/Components/AnchorableComponent.cs b/Content.Server/Construction/Components/AnchorableComponent.cs index 3862bb39c7..2926635144 100644 --- a/Content.Server/Construction/Components/AnchorableComponent.cs +++ b/Content.Server/Construction/Components/AnchorableComponent.cs @@ -4,6 +4,7 @@ using Content.Server.Coordinates.Helpers; using Content.Server.Pulling; using Content.Server.Tools.Components; using Content.Shared.Interaction; +using Content.Shared.Pulling.Components; using Content.Shared.Tool; using Robust.Shared.GameObjects; using Robust.Shared.Physics; @@ -75,7 +76,7 @@ namespace Content.Server.Construction.Components var rot = Owner.Transform.LocalRotation; Owner.Transform.LocalRotation = Math.Round(rot / (Math.PI / 2)) * (Math.PI / 2); - if (Owner.TryGetComponent(out PullableComponent? pullableComponent)) + if (Owner.TryGetComponent(out SharedPullableComponent? pullableComponent)) { if (pullableComponent.Puller != null) { diff --git a/Content.Server/Construction/Components/ConstructionComponent.Verbs.cs b/Content.Server/Construction/Components/ConstructionComponent.Verbs.cs deleted file mode 100644 index b7920cb832..0000000000 --- a/Content.Server/Construction/Components/ConstructionComponent.Verbs.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Content.Shared.ActionBlocker; -using Content.Shared.Popups; -using Content.Shared.Verbs; -using Robust.Shared.GameObjects; -using Robust.Shared.Localization; - -namespace Content.Server.Construction.Components -{ - public partial class ConstructionComponent - { - [Verb] - public sealed class DeconstructibleVerb : Verb - { - protected override void GetData(IEntity user, ConstructionComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - if (((component.Target != null) && (component.Target.Name == component.DeconstructionNodeIdentifier)) || - ((component.Node != null) && (component.Node.Name == component.DeconstructionNodeIdentifier))) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.CategoryData = VerbCategories.Construction; - data.Text = Loc.GetString("deconstructible-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/rotate_ccw.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, ConstructionComponent component) - { - component.SetNewTarget(component.DeconstructionNodeIdentifier); - if (component.Target == null) - { - // Maybe check, but on the flip-side a better solution might be to not make it undeconstructible in the first place, no? - component.Owner.PopupMessage(user, Loc.GetString("deconstructible-verb-activate-no-target-text")); - } - else - { - component.Owner.PopupMessage(user, Loc.GetString("deconstructible-verb-activate-text")); - } - } - } - } -} diff --git a/Content.Server/Construction/ConstructionSystem.cs b/Content.Server/Construction/ConstructionSystem.cs index e45f840309..2fa3757b7c 100644 --- a/Content.Server/Construction/ConstructionSystem.cs +++ b/Content.Server/Construction/ConstructionSystem.cs @@ -17,6 +17,7 @@ using Content.Shared.Coordinates; using Content.Shared.Examine; using Content.Shared.Interaction.Helpers; using Content.Shared.Popups; +using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Shared.Containers; using Robust.Shared.GameObjects; @@ -50,9 +51,42 @@ namespace Content.Server.Construction SubscribeNetworkEvent(HandleStartStructureConstruction); SubscribeNetworkEvent(HandleStartItemConstruction); + SubscribeLocalEvent(AddDeconstructVerb); SubscribeLocalEvent(HandleConstructionExamined); } + private void AddDeconstructVerb(EntityUid uid, ConstructionComponent component, GetOtherVerbsEvent args) + { + if (!args.CanAccess) + return; + + if (component.Target?.Name == component.DeconstructionNodeIdentifier || + component.Node?.Name == component.DeconstructionNodeIdentifier) + return; + + Verb verb = new(); + //verb.Category = VerbCategories.Construction; + //TODO VERBS add more construction verbs? Until then, removing construction category + verb.Text = Loc.GetString("deconstructible-verb-begin-deconstruct"); + verb.IconTexture = "/Textures/Interface/hammer_scaled.svg.192dpi.png"; + + verb.Act = () => + { + component.SetNewTarget(component.DeconstructionNodeIdentifier); + if (component.Target == null) + { + // Maybe check, but on the flip-side a better solution might be to not make it undeconstructible in the first place, no? + component.Owner.PopupMessage(args.User, Loc.GetString("deconstructible-verb-activate-no-target-text")); + } + else + { + component.Owner.PopupMessage(args.User, Loc.GetString("deconstructible-verb-activate-text")); + } + }; + + args.Verbs.Add(verb); + } + private void HandleConstructionExamined(EntityUid uid, ConstructionComponent component, ExaminedEvent args) { if (component.Target != null) @@ -101,7 +135,6 @@ namespace Content.Server.Construction list[0].DoExamine(args); } - } private IEnumerable EnumerateNearby(IEntity user) diff --git a/Content.Server/Containers/Commands/HideContainedContextCommand.cs b/Content.Server/Containers/Commands/HideContainedContextCommand.cs deleted file mode 100644 index 8ec47ad4bf..0000000000 --- a/Content.Server/Containers/Commands/HideContainedContextCommand.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Content.Server.Administration; -using Content.Server.Verbs; -using Content.Shared.Administration; -using Robust.Server.Player; -using Robust.Shared.Console; -using Robust.Shared.GameObjects; - -namespace Content.Server.Containers.Commands -{ - [AdminCommand(AdminFlags.Debug)] - public class HideContainedContextCommand : IConsoleCommand - { - public string Command => "hidecontainedcontext"; - public string Description => $"Reverts the effects of {ShowContainedContextCommand.CommandName}"; - public string Help => $"{Command}"; - - public void Execute(IConsoleShell shell, string argStr, string[] args) - { - var player = shell.Player as IPlayerSession; - if (player == null) - { - shell.WriteLine("You need to be a player to use this command."); - return; - } - - EntitySystem.Get().RemoveContainerVisibility(player); - } - } -} diff --git a/Content.Server/Cuffs/Components/CuffableComponent.cs b/Content.Server/Cuffs/Components/CuffableComponent.cs index 90db2f9b76..1ab0564cc3 100644 --- a/Content.Server/Cuffs/Components/CuffableComponent.cs +++ b/Content.Server/Cuffs/Components/CuffableComponent.cs @@ -313,31 +313,5 @@ namespace Content.Server.Cuffs.Components return; } - - /// - /// Allows the uncuffing of a cuffed person. Used by other people and by the component owner to break out of cuffs. - /// - [Verb] - private sealed class UncuffVerb : Verb - { - protected override void GetData(IEntity user, CuffableComponent component, VerbData data) - { - if ((user != component.Owner && !EntitySystem.Get().CanInteract(user)) || component.CuffedHandCount == 0) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("uncuff-verb-get-data-text"); - } - - protected override void Activate(IEntity user, CuffableComponent component) - { - if (component.CuffedHandCount > 0) - { - component.TryUncuff(user); - } - } - } } } diff --git a/Content.Server/Cuffs/CuffableSystem.cs b/Content.Server/Cuffs/CuffableSystem.cs index dbbbe75efc..a031212903 100644 --- a/Content.Server/Cuffs/CuffableSystem.cs +++ b/Content.Server/Cuffs/CuffableSystem.cs @@ -2,13 +2,14 @@ using Content.Server.Cuffs.Components; using Content.Server.Hands.Components; using Content.Shared.Hands.Components; using Content.Shared.ActionBlocker; -using Content.Shared.MobState; using Content.Shared.Cuffs; using Content.Shared.Popups; +using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.Localization; using Robust.Shared.IoC; +using Content.Shared.MobState; using Robust.Shared.Player; namespace Content.Server.Cuffs @@ -25,6 +26,27 @@ namespace Content.Server.Cuffs SubscribeLocalEvent(OnHandCountChanged); SubscribeLocalEvent(OnUncuffAttempt); + + SubscribeLocalEvent(AddUncuffVerb); + } + + private void AddUncuffVerb(EntityUid uid, CuffableComponent component, GetOtherVerbsEvent args) + { + // Can the user access the cuffs, and is there even anything to uncuff? + if (!args.CanAccess || component.CuffedHandCount == 0) + return; + + // We only check can interact if the user is not uncuffing themselves. As a result, the verb will show up + // when the user is incapacitated & trying to uncuff themselves, but TryUncuff() will still fail when + // attempted. + if (args.User != args.Target && !args.CanInteract) + return; + + Verb verb = new(); + verb.Act = () => component.TryUncuff(args.User); + verb.Text = Loc.GetString("uncuff-verb-get-data-text"); + //TODO VERB ICON add uncuffing symbol? may re-use the alert symbol showing that you are currently cuffed? + args.Verbs.Add(verb); } private void OnUncuffAttempt(UncuffAttemptEvent args) @@ -43,6 +65,7 @@ namespace Content.Server.Cuffs // This is because the CanInteract blocking of the cuffs prevents self-uncuff. if (args.User == args.Target) { + // This UncuffAttemptEvent check should probably be In MobStateSystem, not here? if (userEntity.TryGetComponent(out var state)) { // Manually check this. @@ -54,6 +77,7 @@ namespace Content.Server.Cuffs else { // Uh... let it go through??? + // TODO CUFFABLE/STUN add UncuffAttemptEvent subscription to StunSystem } } else diff --git a/Content.Server/Damage/RejuvenateVerb.cs b/Content.Server/Damage/RejuvenateVerb.cs deleted file mode 100644 index 0462f1af28..0000000000 --- a/Content.Server/Damage/RejuvenateVerb.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Content.Server.Atmos.Components; -using Content.Server.Atmos.EntitySystems; -using Content.Server.Nutrition.Components; -using Content.Server.Nutrition.EntitySystems; -using Content.Server.Stunnable.Components; -using Content.Shared.Damage; -using Content.Shared.MobState; -using Content.Shared.Nutrition.Components; -using Content.Shared.Verbs; -using Robust.Server.Console; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; - -namespace Content.Server.Damage -{ - /// - /// Completely removes all damage from the DamageableComponent (heals the mob). - /// - [GlobalVerb] - public class RejuvenateVerb : GlobalVerb - { - public override bool RequireInteractionRange => false; - public override bool BlockedByContainers => false; - - public override void GetData(IEntity user, IEntity target, VerbData data) - { - data.Text = Loc.GetString("rejuvenate-verb-get-data-text"); - data.CategoryData = VerbCategories.Debug; - data.Visibility = VerbVisibility.Invisible; - data.IconTexture = "/Textures/Interface/VerbIcons/rejuvenate.svg.192dpi.png"; - - var groupController = IoCManager.Resolve(); - - if (user.TryGetComponent(out var player)) - { - if (!target.HasComponent() && !target.HasComponent() && - !target.HasComponent()) - { - return; - } - - if (groupController.CanCommand(player.PlayerSession, "rejuvenate")) - { - data.Visibility = VerbVisibility.Visible; - } - } - } - - public override void Activate(IEntity user, IEntity target) - { - var groupController = IoCManager.Resolve(); - if (user.TryGetComponent(out var player)) - { - if (groupController.CanCommand(player.PlayerSession, "rejuvenate")) - PerformRejuvenate(target); - } - } - - public static void PerformRejuvenate(IEntity target) - { - if (target.TryGetComponent(out DamageableComponent? damageable)) - { - EntitySystem.Get().SetAllDamage(damageable, 0); - } - - if (target.TryGetComponent(out HungerComponent? hunger)) - { - hunger.ResetFood(); - } - - if (target.TryGetComponent(out ThirstComponent? thirst)) - { - thirst.ResetThirst(); - } - - if (target.TryGetComponent(out StunnableComponent? stun)) - { - stun.ResetStuns(); - } - - if (target.TryGetComponent(out FlammableComponent? flammable)) - { - EntitySystem.Get().Extinguish(target.Uid, flammable); - } - - if (target.TryGetComponent(out CreamPiedComponent? creamPied)) - { - EntitySystem.Get().SetCreamPied(target.Uid, creamPied, false); - } - } - } -} diff --git a/Content.Server/Disposal/Tube/Components/DisposalRouterComponent.cs b/Content.Server/Disposal/Tube/Components/DisposalRouterComponent.cs index 3a973d2395..47b720a6d1 100644 --- a/Content.Server/Disposal/Tube/Components/DisposalRouterComponent.cs +++ b/Content.Server/Disposal/Tube/Components/DisposalRouterComponent.cs @@ -185,36 +185,10 @@ namespace Content.Server.Disposal.Tube.Components base.OnRemove(); } - private void OpenUserInterface(ActorComponent actor) + public void OpenUserInterface(ActorComponent actor) { UpdateUserInterface(); UserInterface?.Open(actor.PlayerSession); } - - [Verb] - public sealed class ConfigureVerb : Verb - { - protected override void GetData(IEntity user, DisposalRouterComponent component, VerbData data) - { - var session = user.PlayerSession(); - var groupController = IoCManager.Resolve(); - if (session == null || !groupController.CanAdminMenu(session)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("configure-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/settings.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, DisposalRouterComponent component) - { - if (user.TryGetComponent(out ActorComponent? actor)) - { - component.OpenUserInterface(actor); - } - } - } } } diff --git a/Content.Server/Disposal/Tube/Components/DisposalTaggerComponent.cs b/Content.Server/Disposal/Tube/Components/DisposalTaggerComponent.cs index 2cf41cbf4a..eb44c289eb 100644 --- a/Content.Server/Disposal/Tube/Components/DisposalTaggerComponent.cs +++ b/Content.Server/Disposal/Tube/Components/DisposalTaggerComponent.cs @@ -150,34 +150,7 @@ namespace Content.Server.Disposal.Tube.Components base.OnRemove(); UserInterface?.CloseAll(); } - - [Verb] - public sealed class ConfigureVerb : Verb - { - protected override void GetData(IEntity user, DisposalTaggerComponent component, VerbData data) - { - - var groupController = IoCManager.Resolve(); - if (!user.TryGetComponent(out ActorComponent? actor) || !groupController.CanAdminMenu(actor.PlayerSession)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("configure-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/settings.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, DisposalTaggerComponent component) - { - if (user.TryGetComponent(out ActorComponent? actor)) - { - component.OpenUserInterface(actor); - } - } - } - - private void OpenUserInterface(ActorComponent actor) + public void OpenUserInterface(ActorComponent actor) { UpdateUserInterface(); UserInterface?.Open(actor.PlayerSession); diff --git a/Content.Server/Disposal/Tube/Components/DisposalTubeComponent.cs b/Content.Server/Disposal/Tube/Components/DisposalTubeComponent.cs index 89adacf96f..01ead3d2cf 100644 --- a/Content.Server/Disposal/Tube/Components/DisposalTubeComponent.cs +++ b/Content.Server/Disposal/Tube/Components/DisposalTubeComponent.cs @@ -252,39 +252,5 @@ namespace Content.Server.Disposal.Tube.Components Disconnect(); UpdateVisualState(); } - - [Verb] - private sealed class TubeDirectionsVerb : Verb - { - protected override void GetData(IEntity user, IDisposalTubeComponent component, VerbData data) - { - data.Text = Loc.GetString("tube-direction-verb-get-data-text"); - data.CategoryData = VerbCategories.Debug; - data.Visibility = VerbVisibility.Invisible; - - var groupController = IoCManager.Resolve(); - - if (user.TryGetComponent(out var player)) - { - if (groupController.CanCommand(player.PlayerSession, "tubeconnections")) - { - data.Visibility = VerbVisibility.Visible; - } - } - } - - protected override void Activate(IEntity user, IDisposalTubeComponent component) - { - var groupController = IoCManager.Resolve(); - - if (user.TryGetComponent(out var player)) - { - if (groupController.CanCommand(player.PlayerSession, "tubeconnections")) - { - component.PopupDirections(user); - } - } - } - } } } diff --git a/Content.Server/Disposal/Tube/DisposalTubeSystem.cs b/Content.Server/Disposal/Tube/DisposalTubeSystem.cs index 4d0697ae6b..7454b4ba1e 100644 --- a/Content.Server/Disposal/Tube/DisposalTubeSystem.cs +++ b/Content.Server/Disposal/Tube/DisposalTubeSystem.cs @@ -1,8 +1,11 @@ using Content.Server.Disposal.Tube.Components; using Content.Shared.Movement; +using Content.Shared.Verbs; +using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Player; using Robust.Shared.Timing; @@ -17,7 +20,42 @@ namespace Content.Server.Disposal.Tube base.Initialize(); SubscribeLocalEvent(BodyTypeChanged); + SubscribeLocalEvent(OnRelayMovement); + SubscribeLocalEvent(AddOpenUIVerbs); + SubscribeLocalEvent(AddOpenUIVerbs); + } + + private void AddOpenUIVerbs(EntityUid uid, DisposalTaggerComponent component, GetInteractionVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + if (!args.User.TryGetComponent(out var actor)) + return; + var player = actor.PlayerSession; + + Verb verb = new(); + verb.Text = Loc.GetString("configure-verb-get-data-text"); + verb.IconTexture = "/Textures/Interface/VerbIcons/settings.svg.192dpi.png"; + verb.Act = () => component.OpenUserInterface(actor); + args.Verbs.Add(verb); + } + + private void AddOpenUIVerbs(EntityUid uid, DisposalRouterComponent component, GetInteractionVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + if (!args.User.TryGetComponent(out var actor)) + return; + var player = actor.PlayerSession; + + Verb verb = new(); + verb.Text = Loc.GetString("configure-verb-get-data-text"); + verb.IconTexture = "/Textures/Interface/VerbIcons/settings.svg.192dpi.png"; + verb.Act = () => component.OpenUserInterface(actor); + args.Verbs.Add(verb); } private void OnRelayMovement(EntityUid uid, DisposalTubeComponent component, RelayMovementEntityEvent args) diff --git a/Content.Server/Disposal/Unit/Components/DisposalUnitComponent.cs b/Content.Server/Disposal/Unit/Components/DisposalUnitComponent.cs index f14f0999b7..37ffb69a57 100644 --- a/Content.Server/Disposal/Unit/Components/DisposalUnitComponent.cs +++ b/Content.Server/Disposal/Unit/Components/DisposalUnitComponent.cs @@ -193,84 +193,6 @@ namespace Content.Server.Disposal.Unit.Components return true; } - [Verb] - private sealed class SelfInsertVerb : Verb - { - protected override void GetData(IEntity user, DisposalUnitComponent component, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - if (!EntitySystem.Get().CanInteract(user) || - component.ContainedEntities.Contains(user)) - { - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("disposal-self-insert-verb-get-data-text"); - } - - protected override void Activate(IEntity user, DisposalUnitComponent component) - { - _ = component.TryInsert(user, user); - } - } - - [Verb] - private sealed class FlushVerb : Verb - { - protected override void GetData(IEntity user, DisposalUnitComponent component, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - if (!EntitySystem.Get().CanInteract(user) || - component.ContainedEntities.Contains(user)) - { - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("disposal-flush-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, DisposalUnitComponent component) - { - EntitySystem.Get().Engage(component); - } - } - - [Verb] - private sealed class EjectVerb : Verb - { - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, DisposalUnitComponent component, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - if (!EntitySystem.Get().CanInteract(user) || - component.ContainedEntities.Contains(user)) - { - return; - } - - // Only show verb if actually containing any entities. - if (component.ContainedEntities.Count > 0) - data.Visibility = VerbVisibility.Visible; - else - data.Visibility = VerbVisibility.Invisible; - - data.Text = Loc.GetString("disposal-eject-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, DisposalUnitComponent component) - { - EntitySystem.Get().TryEjectContents(component); - } - } - void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs) { EntitySystem.Get().TryEjectContents(this); diff --git a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs index 9757ed2d9e..a8cd0c5935 100644 --- a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs +++ b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs @@ -26,6 +26,7 @@ using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Random; +using Content.Shared.Verbs; namespace Content.Server.Disposal.Unit.EntitySystems { @@ -33,7 +34,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems { [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; - + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly AtmosphereSystem _atmosSystem = default!; private readonly List _activeDisposals = new(); @@ -58,6 +59,51 @@ namespace Content.Server.Disposal.Unit.EntitySystems SubscribeLocalEvent(HandleActivate); SubscribeLocalEvent(HandleInteractHand); SubscribeLocalEvent(HandleInteractUsing); + + // Verbs + SubscribeLocalEvent(AddFlushEjectVerbs); + SubscribeLocalEvent(AddClimbInsideVerb); + } + private void AddFlushEjectVerbs(EntityUid uid, DisposalUnitComponent component, GetAlternativeVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || component.ContainedEntities.Count == 0) + return; + + // Verbs to flush the unit + Verb flushVerb = new(); + flushVerb.Act = () => Engage(component); + flushVerb.Text = Loc.GetString("disposal-flush-verb-get-data-text"); + flushVerb.IconTexture = "/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png"; + flushVerb.Priority = 1; + args.Verbs.Add(flushVerb); + + // Verb to eject the contents + Verb ejectVerb = new(); + ejectVerb.Act = () => TryEjectContents(component); + ejectVerb.Category = VerbCategory.Eject; + ejectVerb.Text = Loc.GetString("disposal-eject-verb-contents"); + args.Verbs.Add(ejectVerb); + } + + private void AddClimbInsideVerb(EntityUid uid, DisposalUnitComponent component, GetOtherVerbsEvent args) + { + // This is not an interaction, activation, or alternative verb type because unfortunately most users are + // unwilling to accept that this is where they belong and don't want to accidentally climb inside. + if (!args.CanAccess || + !args.CanInteract || + component.ContainedEntities.Contains(args.User) || + !_actionBlockerSystem.CanMove(args.User)) + return; + + // Add verb to climb inside of the unit, + Verb verb = new(); + verb.Act = () => component.TryInsert(args.User, args.User); + verb.Text = Loc.GetString("disposal-self-insert-verb-get-data-text"); + // TODO VERN ICON + // TODO VERB CATEGORY + // create a verb category for "enter"? + // See also, medical scanner. Also maybe add verbs for entering lockers/body bags? + args.Verbs.Add(verb); } public override void Update(float frameTime) diff --git a/Content.Server/Fluids/Components/SpillableComponent.cs b/Content.Server/Fluids/Components/SpillableComponent.cs index 0a2ac3dd93..7b0af8c69c 100644 --- a/Content.Server/Fluids/Components/SpillableComponent.cs +++ b/Content.Server/Fluids/Components/SpillableComponent.cs @@ -1,12 +1,6 @@ -using Content.Shared.ActionBlocker; -using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Chemistry.Reagent; using Content.Shared.Interaction; -using Content.Shared.Popups; -using Content.Shared.Verbs; using Robust.Shared.GameObjects; -using Robust.Shared.Localization; using Robust.Shared.Serialization.Manager.Attributes; namespace Content.Server.Fluids.Components @@ -19,56 +13,6 @@ namespace Content.Server.Fluids.Components [DataField("solution")] public string SolutionName = "puddle"; - /// - /// Transfers solution from the held container to the floor. - /// - [Verb] - private sealed class SpillTargetVerb : Verb - { - protected override void GetData(IEntity user, SpillableComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user) || - !EntitySystem.Get() - .TryGetDrainableSolution(component.Owner.Uid, out var solutionComponent)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("spill-target-verb-get-data-text"); - data.Visibility = solutionComponent.DrainAvailable > ReagentUnit.Zero - ? VerbVisibility.Visible - : VerbVisibility.Disabled; - } - - protected override void Activate(IEntity user, SpillableComponent component) - { - var solutionsSys = EntitySystem.Get(); - if (component.Owner.HasComponent()) - { - if (solutionsSys.TryGetDrainableSolution(component.Owner.Uid, out var solutionComponent)) - { - if (solutionComponent.DrainAvailable <= 0) - { - user.PopupMessage(user, - Loc.GetString("spill-target-verb-activate-is-empty-message", ("owner", component.Owner))); - } - - // Need this as when we split the component's owner may be deleted - EntitySystem.Get() - .Drain(component.Owner.Uid, solutionComponent, solutionComponent.DrainAvailable) - .SpillAt(component.Owner.Transform.Coordinates, "PuddleSmear"); - } - else - { - user.PopupMessage(user, - Loc.GetString("spill-target-verb-activate-cannot-drain-message", - ("owner", component.Owner))); - } - } - } - } - void IDropped.Dropped(DroppedEventArgs eventArgs) { if (!eventArgs.Intentional diff --git a/Content.Server/Fluids/PuddleSystem.cs b/Content.Server/Fluids/PuddleSystem.cs index c019f12a09..b526b7825c 100644 --- a/Content.Server/Fluids/PuddleSystem.cs +++ b/Content.Server/Fluids/PuddleSystem.cs @@ -1,4 +1,7 @@ using Content.Server.Fluids.Components; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Verbs; using Content.Shared.Examine; using Content.Shared.Slippery; using JetBrains.Annotations; @@ -13,12 +16,14 @@ namespace Content.Server.Fluids internal sealed class PuddleSystem : EntitySystem { [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; public override void Initialize() { base.Initialize(); _mapManager.TileChanged += HandleTileChanged; + SubscribeLocalEvent(AddSpillVerb); SubscribeLocalEvent(HandlePuddleExamined); } @@ -28,6 +33,25 @@ namespace Content.Server.Fluids _mapManager.TileChanged -= HandleTileChanged; } + private void AddSpillVerb(EntityUid uid, SpillableComponent component, GetOtherVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + if (!_solutionContainerSystem.TryGetDrainableSolution(args.Target.Uid, out var solution)) + return; + + if (solution.DrainAvailable == ReagentUnit.Zero) + return; + + Verb verb = new(); + verb.Text = Loc.GetString("spill-target-verb-get-data-text"); + // TODO VERB ICONS spill icon? pouring out a glass/beaker? + verb.Act = () => _solutionContainerSystem.SplitSolution(args.Target.Uid, + solution, solution.DrainAvailable).SpillAt(args.Target.Transform.Coordinates, "PuddleSmear"); + args.Verbs.Add(verb); + } + private void HandlePuddleExamined(EntityUid uid, PuddleComponent component, ExaminedEvent args) { if (EntityManager.TryGetComponent(uid, out var slippery) && slippery.Slippery) diff --git a/Content.Server/Ghost/Roles/MakeGhostRoleVerb.cs b/Content.Server/Ghost/Roles/MakeGhostRoleVerb.cs deleted file mode 100644 index 32b424e9ce..0000000000 --- a/Content.Server/Ghost/Roles/MakeGhostRoleVerb.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Content.Server.Mind.Components; -using Content.Shared.Verbs; -using Robust.Server.Console; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; - -namespace Content.Server.Ghost.Roles -{ - [GlobalVerb] - public class MakeGhostRoleVerb : GlobalVerb - { - public override bool RequireInteractionRange => false; - public override bool BlockedByContainers => false; - - public override void GetData(IEntity user, IEntity target, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - var groupController = IoCManager.Resolve(); - - if (target.TryGetComponent(out MindComponent? mind) && - mind.HasMind) - { - return; - } - - if (!user.TryGetComponent(out ActorComponent? actor) || - !groupController.CanCommand(actor.PlayerSession, "makeghostrole")) - { - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("make-ghost-role-verb-get-data-text"); - data.CategoryData = VerbCategories.Debug; - } - - public override void Activate(IEntity user, IEntity target) - { - var groupController = IoCManager.Resolve(); - - if (target.TryGetComponent(out MindComponent? mind) && - mind.HasMind) - { - return; - } - - if (!user.TryGetComponent(out ActorComponent? actor) || - !groupController.CanCommand(actor.PlayerSession, "makeghostrole")) - { - return; - } - - var ghostRoleSystem = EntitySystem.Get(); - ghostRoleSystem.OpenMakeGhostRoleEui(actor.PlayerSession, target.Uid); - } - } -} diff --git a/Content.Server/Hands/Components/HandsComponent.cs b/Content.Server/Hands/Components/HandsComponent.cs index fb93535cde..a92c6e0ad7 100644 --- a/Content.Server/Hands/Components/HandsComponent.cs +++ b/Content.Server/Hands/Components/HandsComponent.cs @@ -163,7 +163,7 @@ namespace Content.Server.Hands.Components { // What is this API?? if (!Owner.TryGetComponent(out SharedPullerComponent? puller) - || puller.Pulling == null || !puller.Pulling.TryGetComponent(out PullableComponent? pullable)) + || puller.Pulling == null || !puller.Pulling.TryGetComponent(out SharedPullableComponent? pullable)) return false; return _entitySystemManager.GetEntitySystem().TryStopPull(pullable); diff --git a/Content.Server/Interaction/InRangeUnoccludedVerb.cs b/Content.Server/Interaction/InRangeUnoccludedVerb.cs deleted file mode 100644 index 00a06f229b..0000000000 --- a/Content.Server/Interaction/InRangeUnoccludedVerb.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Content.Shared.Interaction.Helpers; -using Content.Shared.Popups; -using Content.Shared.Verbs; -using Robust.Server.Console; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; - -namespace Content.Server.Interaction -{ - [GlobalVerb] - public class InRangeUnoccludedVerb : GlobalVerb - { - public override bool RequireInteractionRange => false; - - public override void GetData(IEntity user, IEntity target, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - if (!user.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - var groupController = IoCManager.Resolve(); - if (!groupController.CanCommand(actor.PlayerSession, "inrangeunoccluded")) - { - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("in-range-unoccluded-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/information.svg.192dpi.png"; - data.CategoryData = VerbCategories.Debug; - } - - public override void Activate(IEntity user, IEntity target) - { - if (!user.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - var groupController = IoCManager.Resolve(); - if (!groupController.CanCommand(actor.PlayerSession, "inrangeunoccluded")) - { - return; - } - - var message = user.InRangeUnOccluded(target) - ? Loc.GetString("in-range-unoccluded-verb-on-activate-not-occluded") - : Loc.GetString("in-range-unoccluded-verb-on-activate-occluded"); - - target.PopupMessage(user, message); - } - } -} diff --git a/Content.Server/Interaction/InteractionSystem.cs b/Content.Server/Interaction/InteractionSystem.cs index 32e55c5c35..ecc47406bf 100644 --- a/Content.Server/Interaction/InteractionSystem.cs +++ b/Content.Server/Interaction/InteractionSystem.cs @@ -8,6 +8,7 @@ using Content.Server.Hands.Components; using Content.Server.Items; using Content.Server.Pulling; using Content.Server.Timing; +using Content.Server.Verbs; using Content.Shared.ActionBlocker; using Content.Shared.DragDrop; using Content.Shared.Hands; @@ -17,6 +18,7 @@ using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Content.Shared.Inventory; using Content.Shared.Popups; +using Content.Shared.Pulling.Components; using Content.Shared.Rotatable; using Content.Shared.Throwing; using Content.Shared.Verbs; @@ -47,6 +49,7 @@ namespace Content.Server.Interaction [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + [Dependency] private readonly VerbSystem _verbSystem = default!; [Dependency] private readonly PullingSystem _pullSystem = default!; public override void Initialize() @@ -311,7 +314,7 @@ namespace Content.Server.Interaction if (!InRangeUnobstructed(userEntity, pulledObject, popup: true)) return false; - if (!pulledObject.TryGetComponent(out PullableComponent? pull)) + if (!pulledObject.TryGetComponent(out SharedPullableComponent? pull)) return false; return _pullSystem.TogglePull(userEntity, pull); @@ -380,13 +383,7 @@ namespace Content.Server.Interaction { // We are close to the nearby object. if (altInteract) - // We are trying to use alternative interactions. Perform alternative interactions, using context - // menu verbs. - - // Verbs can be triggered with an item in the hand, but currently there are no verbs that depend on - // the currently held item. Maybe this if statement should be changed to - // (altInteract && (item == null || item == target)). - // Note that item == target will happen when alt-clicking the item currently in your hands. + // Perform alternative interactions, using context menu verbs. AltInteract(user, target); else if (item != null && item != target) // We are performing a standard interaction with an item, and the target isn't the same as the item @@ -428,7 +425,7 @@ namespace Content.Server.Interaction if (user.TryGetComponent(out BuckleComponent? buckle) && (buckle.BuckledTo != null)) { // We're buckled to another object. Is that object rotatable? - if (buckle.BuckledTo!.Owner.TryGetComponent(out SharedRotatableComponent? rotatable) && rotatable.RotateWhileAnchored) + if (buckle.BuckledTo!.Owner.TryGetComponent(out RotatableComponent? rotatable) && rotatable.RotateWhileAnchored) { // Note the assumption that even if unanchored, user can only do spinnychair with an "independent wheel". // (Since the user being buckled to it holds it down with their weight.) @@ -511,36 +508,20 @@ namespace Content.Server.Interaction /// Alternative interactions on an entity. /// /// - /// Uses the context menu verb list, and acts out the first verb marked as an alternative interaction. Note - /// that this does not have any checks to see whether this interaction is valid, as these are all done in + /// Uses the context menu verb list, and acts out the highest priority alternative interaction verb. /// public void AltInteract(IEntity user, IEntity target) { - // TODO VERB SYSTEM when ECS-ing verbs and re-writing VerbUtility.GetVerbs, maybe sort verbs by some - // priority property, such that which verbs appear first is more predictable?. + // Get list of alt-interact verbs + GetAlternativeVerbsEvent getVerbEvent = new(user, target); + RaiseLocalEvent(target.Uid, getVerbEvent); - // Iterate through list of verbs that apply to target. We do not include global verbs here. If in the future - // alt click should also support global verbs, this needs to be changed. - foreach (var (component, verb) in VerbUtility.GetVerbs(target)) + foreach (var verb in getVerbEvent.Verbs) { - // Check that the verb marked as an alternative interaction? - if (!verb.AlternativeInteraction) + if (verb.Disabled) continue; - // Can the verb be acted out? - if (!VerbUtility.VerbAccessChecks(user, target, verb)) - continue; - - // Is the verb currently enabled? - var verbData = verb.GetData(user, component); - if (verbData.IsInvisible || verbData.IsDisabled) - continue; - - // Act out the verb. Note that, if there is more than one AlternativeInteraction verb, only the first - // one is activated. The priority is effectively determined by the order in which VerbUtility.GetVerbs() - // returns the verbs. - verb.Activate(user, component); + _verbSystem.TryExecuteVerb(verb); break; } } diff --git a/Content.Server/Inventory/Components/InventoryComponent.cs b/Content.Server/Inventory/Components/InventoryComponent.cs index c6c9bb5cb3..b6c56dfeb6 100644 --- a/Content.Server/Inventory/Components/InventoryComponent.cs +++ b/Content.Server/Inventory/Components/InventoryComponent.cs @@ -614,47 +614,5 @@ namespace Content.Server.Inventory.Components return false; } - - [Verb] - private sealed class SetOutfitVerb : Verb - { - public override bool RequireInteractionRange => false; - public override bool BlockedByContainers => false; - - protected override void GetData(IEntity user, InventoryComponent component, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - if (!CanCommand(user)) - return; - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("set-outfit-verb-get-data-text"); - data.CategoryData = VerbCategories.Debug; - data.IconTexture = "/Textures/Interface/VerbIcons/outfit.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, InventoryComponent component) - { - if (!CanCommand(user)) - return; - - var target = component.Owner; - - var entityId = target.Uid.ToString(); - - var command = new SetOutfitCommand(); - var host = IoCManager.Resolve(); - var args = new string[] {entityId}; - var session = user.PlayerSession(); - command.Execute(new ConsoleShell(host, session), $"{command.Command} {entityId}", args); - } - - private static bool CanCommand(IEntity user) - { - var groupController = IoCManager.Resolve(); - return user.TryGetComponent(out var player) && - groupController.CanCommand(player.PlayerSession, "setoutfit"); - } - } } } diff --git a/Content.Server/Items/ItemComponent.cs b/Content.Server/Items/ItemComponent.cs index 02d5f8eeca..63c5fad6a2 100644 --- a/Content.Server/Items/ItemComponent.cs +++ b/Content.Server/Items/ItemComponent.cs @@ -1,12 +1,6 @@ -using Content.Server.Hands.Components; -using Content.Shared.ActionBlocker; -using Content.Shared.Interaction.Events; using Content.Shared.Item; -using Content.Shared.Verbs; using Robust.Server.GameObjects; -using Robust.Shared.Containers; using Robust.Shared.GameObjects; -using Robust.Shared.Localization; namespace Content.Server.Items { @@ -29,32 +23,6 @@ namespace Content.Server.Items component.Visible = false; } } - - [Verb] - public sealed class PickUpVerb : Verb - { - protected override void GetData(IEntity user, ItemComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user) || - component.Owner.IsInContainer() || - !component.CanPickup(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("pick-up-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/pickup.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, ItemComponent component) - { - if (user.TryGetComponent(out HandsComponent? hands) && !hands.IsHolding(component.Owner)) - { - hands.PutInHand(component); - } - } - } } } diff --git a/Content.Server/Items/ItemSystem.cs b/Content.Server/Items/ItemSystem.cs new file mode 100644 index 0000000000..2f03fb5338 --- /dev/null +++ b/Content.Server/Items/ItemSystem.cs @@ -0,0 +1,39 @@ +using Content.Shared.Item; +using Content.Shared.Verbs; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; + +namespace Content.Server.Items +{ + public class ItemSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(AddPickupVerb); + } + + private void AddPickupVerb(EntityUid uid, SharedItemComponent component, GetInteractionVerbsEvent args) + { + if (args.Hands == null || + args.Using != null || + !args.CanAccess || + !args.CanInteract || + !component.CanPickup(args.User, popup: false)) + return; + + Verb verb = new(); + verb.Act = () => args.Hands.TryPutInActiveHandOrAny(args.Target); + verb.IconTexture = "/Textures/Interface/VerbIcons/pickup.svg.192dpi.png"; + + // if the item already in the user's inventory, change the text + if (args.Target.TryGetContainer(out var container) && container.Owner == args.User) + verb.Text = Loc.GetString("pick-up-verb-get-data-text-inventory"); + else + verb.Text = Loc.GetString("pick-up-verb-get-data-text"); + + args.Verbs.Add(verb); + } + } +} diff --git a/Content.Server/Light/Components/ExpendableLightComponent.cs b/Content.Server/Light/Components/ExpendableLightComponent.cs index 1eb5a47b15..7fbe58c860 100644 --- a/Content.Server/Light/Components/ExpendableLightComponent.cs +++ b/Content.Server/Light/Components/ExpendableLightComponent.cs @@ -1,11 +1,7 @@ using Content.Server.Clothing.Components; using Content.Server.Items; -using Content.Server.Sound.Components; -using Content.Shared.ActionBlocker; using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; using Content.Shared.Light.Component; -using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.GameObjects; @@ -52,7 +48,7 @@ namespace Content.Server.Light.Components /// /// Enables the light if it is not active. Once active it cannot be turned off. /// - private bool TryActivate() + public bool TryActivate() { if (!Activated && CurrentState == ExpendableLightState.BrandNew) { @@ -173,33 +169,5 @@ namespace Content.Server.Light.Components } } } - - [Verb] - public sealed class ActivateVerb : Verb - { - protected override void GetData(IEntity user, ExpendableLightComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - if (component.CurrentState == ExpendableLightState.BrandNew) - { - data.Text = "Activate"; - data.Visibility = VerbVisibility.Visible; - } - else - { - data.Visibility = VerbVisibility.Invisible; - } - } - - protected override void Activate(IEntity user, ExpendableLightComponent component) - { - component.TryActivate(); - } - } } } diff --git a/Content.Server/Light/Components/HandheldLightComponent.cs b/Content.Server/Light/Components/HandheldLightComponent.cs index 0b74f9e056..4ee50002c7 100644 --- a/Content.Server/Light/Components/HandheldLightComponent.cs +++ b/Content.Server/Light/Components/HandheldLightComponent.cs @@ -107,7 +107,7 @@ namespace Content.Server.Light.Components return Activated ? TurnOff() : TurnOn(user); } - private bool TurnOff(bool makeNoise = true) + public bool TurnOff(bool makeNoise = true) { if (!Activated) { @@ -127,7 +127,7 @@ namespace Content.Server.Light.Components return true; } - private bool TurnOn(IEntity user) + public bool TurnOn(IEntity user) { if (Activated) { @@ -243,26 +243,6 @@ namespace Content.Server.Light.Components { return new HandheldLightComponentState(GetLevel()); } - - [Verb] - public sealed class ToggleLightVerb : Verb - { - protected override void GetData(IEntity user, HandheldLightComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("toggle-light-verb-get-data-text"); - } - - protected override void Activate(IEntity user, HandheldLightComponent component) - { - component.ToggleStatus(user); - } - } } [UsedImplicitly] diff --git a/Content.Server/Light/Components/UnpoweredFlashlightComponent.cs b/Content.Server/Light/Components/UnpoweredFlashlightComponent.cs index 7d4bac7b21..c7a65fcefb 100644 --- a/Content.Server/Light/Components/UnpoweredFlashlightComponent.cs +++ b/Content.Server/Light/Components/UnpoweredFlashlightComponent.cs @@ -1,9 +1,5 @@ -using Content.Server.Light.EntitySystems; -using Content.Shared.ActionBlocker; using Content.Shared.Sound; -using Content.Shared.Verbs; using Robust.Shared.GameObjects; -using Robust.Shared.Localization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -22,23 +18,5 @@ namespace Content.Server.Light.Components public SoundSpecifier ToggleSound = new SoundPathSpecifier("/Audio/Items/flashlight_pda.ogg"); [ViewVariables] public bool LightOn = false; - - [Verb] - public sealed class ToggleFlashlightVerb : Verb - { - protected override void GetData(IEntity user, UnpoweredFlashlightComponent component, VerbData data) - { - var canInteract = EntitySystem.Get().CanInteract(user); - - data.Visibility = canInteract ? VerbVisibility.Visible : VerbVisibility.Invisible; - data.Text = Loc.GetString("toggle-flashlight-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/light.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, UnpoweredFlashlightComponent component) - { - EntitySystem.Get().ToggleLight(component); - } - } } } diff --git a/Content.Server/Light/EntitySystems/ExpendableLightSystem.cs b/Content.Server/Light/EntitySystems/ExpendableLightSystem.cs index b49d1e0178..150e31c080 100644 --- a/Content.Server/Light/EntitySystems/ExpendableLightSystem.cs +++ b/Content.Server/Light/EntitySystems/ExpendableLightSystem.cs @@ -1,6 +1,9 @@ -using Content.Server.Light.Components; +using Content.Server.Light.Components; +using Content.Shared.Light.Component; +using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.Localization; namespace Content.Server.Light.EntitySystems { @@ -14,5 +17,29 @@ namespace Content.Server.Light.EntitySystems light.Update(frameTime); } } + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(AddIgniteVerb); + } + + private void AddIgniteVerb(EntityUid uid, ExpendableLightComponent component, GetActivationVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + if (component.CurrentState != ExpendableLightState.BrandNew) + return; + + // Ignite the flare or make the glowstick glow. + // Also hot damn, those are some shitty glowsticks, we need to get a refund. + Verb verb = new(); + verb.Text = Loc.GetString("expendable-light-start-verb"); + verb.IconTexture = "/Textures/Interface/VerbIcons/light.svg.192dpi.png"; + verb.Act = () => component.TryActivate(); + args.Verbs.Add(verb); + } } } diff --git a/Content.Server/Light/EntitySystems/HandHeldLightSystem.cs b/Content.Server/Light/EntitySystems/HandHeldLightSystem.cs index e1b16e2688..8ddb8fec4e 100644 --- a/Content.Server/Light/EntitySystems/HandHeldLightSystem.cs +++ b/Content.Server/Light/EntitySystems/HandHeldLightSystem.cs @@ -1,8 +1,10 @@ using System.Collections.Generic; using System.Linq; using Content.Server.Light.Components; +using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.Localization; namespace Content.Server.Light.EntitySystems { @@ -18,6 +20,7 @@ namespace Content.Server.Light.EntitySystems base.Initialize(); SubscribeLocalEvent(HandleActivate); SubscribeLocalEvent(HandleDeactivate); + SubscribeLocalEvent(AddToggleLightVerb); } public override void Shutdown() @@ -44,5 +47,20 @@ namespace Content.Server.Light.EntitySystems handheld.OnUpdate(frameTime); } } + + private void AddToggleLightVerb(EntityUid uid, HandheldLightComponent component, GetActivationVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + Verb verb = new(); + verb.Text = Loc.GetString("verb-toggle-light"); + verb.IconTexture = "/Textures/Interface/VerbIcons/light.svg.192dpi.png"; + verb.Act = component.Activated + ? () => component.TurnOff() + : () => component.TurnOn(args.User); + + args.Verbs.Add(verb); + } } } diff --git a/Content.Server/Light/EntitySystems/UnpoweredFlashlightSystem.cs b/Content.Server/Light/EntitySystems/UnpoweredFlashlightSystem.cs index da42d4c0e3..642676ea41 100644 --- a/Content.Server/Light/EntitySystems/UnpoweredFlashlightSystem.cs +++ b/Content.Server/Light/EntitySystems/UnpoweredFlashlightSystem.cs @@ -1,9 +1,11 @@ using Content.Server.Light.Components; using Content.Server.Light.Events; using Content.Shared.Light; +using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.GameObjects; +using Robust.Shared.Localization; using Robust.Shared.Player; using System; @@ -11,6 +13,27 @@ namespace Content.Server.Light.EntitySystems { public class UnpoweredFlashlightSystem : EntitySystem { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(AddToggleLightVerbs); + } + + private void AddToggleLightVerbs(EntityUid uid, UnpoweredFlashlightComponent component, GetActivationVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + Verb verb = new(); + verb.Text = Loc.GetString("toggle-flashlight-verb-get-data-text"); + verb.IconTexture = "/Textures/Interface/VerbIcons/light.svg.192dpi.png"; + verb.Act = () => ToggleLight(component); + verb.Priority = -1; // For things like PDA's, Open-UI and other verbs that should be higher priority. + + args.Verbs.Add(verb); + } + public void ToggleLight(UnpoweredFlashlightComponent flashlight) { if (!flashlight.Owner.TryGetComponent(out PointLightComponent? light)) diff --git a/Content.Server/Lock/LockComponent.cs b/Content.Server/Lock/LockComponent.cs index 2fa8f55d44..1cb93fda01 100644 --- a/Content.Server/Lock/LockComponent.cs +++ b/Content.Server/Lock/LockComponent.cs @@ -1,11 +1,5 @@ -using Content.Server.Lock; -using Content.Shared.ActionBlocker; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Helpers; using Content.Shared.Sound; -using Content.Shared.Verbs; using Robust.Shared.GameObjects; -using Robust.Shared.Localization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -23,44 +17,5 @@ namespace Content.Server.Storage.Components [ViewVariables(VVAccess.ReadWrite)] [DataField("lockOnClick")] public bool LockOnClick { get; set; } = false; [ViewVariables(VVAccess.ReadWrite)] [DataField("unlockingSound")] public SoundSpecifier UnlockSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg"); [ViewVariables(VVAccess.ReadWrite)] [DataField("lockingSound")] public SoundSpecifier LockSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg"); - - [Verb] - private sealed class ToggleLockVerb : Verb - { - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, LockComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user) || - component.Owner.TryGetComponent(out EntityStorageComponent? entityStorageComponent) && entityStorageComponent.Open) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString(component.Locked ? "toggle-lock-verb-unlock" : "toggle-lock-verb-lock"); - } - - protected override void Activate(IEntity user, LockComponent component) - { - // Do checks - if (!EntitySystem.Get().CanInteract(user) || - !user.InRangeUnobstructed(component)) - { - return; - } - - // Call relevant entity system - var lockSystem = user.EntityManager.EntitySysManager.GetEntitySystem(); - if (component.Locked) - { - lockSystem.DoUnlock(component.Owner.Uid, user, component); - } - else - { - lockSystem.DoLock(component.Owner.Uid, user, component); - } - } - } } } diff --git a/Content.Server/Lock/LockSystem.cs b/Content.Server/Lock/LockSystem.cs index 438a704b67..00fa900829 100644 --- a/Content.Server/Lock/LockSystem.cs +++ b/Content.Server/Lock/LockSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Storage; +using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -26,6 +27,7 @@ namespace Content.Server.Lock SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnActivated); SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(AddToggleLockVerb); } private void OnStartup(EntityUid uid, LockComponent lockComp, ComponentStartup args) @@ -38,14 +40,17 @@ namespace Content.Server.Lock private void OnActivated(EntityUid uid, LockComponent lockComp, ActivateInWorldEvent args) { + if (args.Handled) + return; + // Only attempt an unlock by default on Activate if (lockComp.Locked) { - DoUnlock(uid, args.User, lockComp); + args.Handled = TryUnlock(uid, args.User, lockComp); } else if (lockComp.LockOnClick) { - DoLock(uid, args.User, lockComp); + args.Handled = TryLock(uid, args.User, lockComp); } } @@ -57,16 +62,20 @@ namespace Content.Server.Lock ("entityName", lockComp.Owner.Name))); } - public bool DoLock(EntityUid uid, IEntity user, LockComponent? lockComp = null) + public bool TryLock(EntityUid uid, IEntity user, LockComponent? lockComp = null) { if (!Resolve(uid, ref lockComp)) return false; - - if (!HasUserAccess(uid, user)) + + if (!CanToggleLock(uid, user, quiet: false)) + return false; + + if (!HasUserAccess(uid, user, quiet: false)) return false; lockComp.Owner.PopupMessage(user, Loc.GetString("lock-comp-do-lock-success", ("entityName",lockComp.Owner.Name))); lockComp.Locked = true; + if(lockComp.LockSound != null) { SoundSystem.Play(Filter.Pvs(lockComp.Owner), lockComp.LockSound.GetSound(), lockComp.Owner, AudioParams.Default.WithVolume(-5)); @@ -82,16 +91,20 @@ namespace Content.Server.Lock return true; } - public bool DoUnlock(EntityUid uid, IEntity user, LockComponent? lockComp = null) + public bool TryUnlock(EntityUid uid, IEntity user, LockComponent? lockComp = null) { if (!Resolve(uid, ref lockComp)) return false; - - if (!HasUserAccess(uid, user)) + + if (!CanToggleLock(uid, user, quiet: false)) + return false; + + if (!HasUserAccess(uid, user, quiet: false)) return false; lockComp.Owner.PopupMessage(user, Loc.GetString("lock-comp-do-unlock-success", ("entityName", lockComp.Owner.Name))); lockComp.Locked = false; + if(lockComp.UnlockSound != null) { SoundSystem.Play(Filter.Pvs(lockComp.Owner), lockComp.UnlockSound.GetSound(), lockComp.Owner, AudioParams.Default.WithVolume(-5)); @@ -104,11 +117,26 @@ namespace Content.Server.Lock RaiseLocalEvent(lockComp.Owner.Uid, new LockToggledEvent(false)); - // To stop EntityStorageComponent from opening right after the container gets unlocked return true; } - private bool HasUserAccess(EntityUid uid, IEntity user, AccessReader? reader = null) + public bool CanToggleLock(EntityUid uid, IEntity user, EntityStorageComponent? storage = null, bool quiet = true) + { + if (!Resolve(uid, ref storage)) + return true; + + // Cannot lock if the entity is currently opened. + if (storage.Open) + return false; + + // Cannot (un)lock from the inside. Maybe a bad idea? Security jocks could trap nerds in lockers? + if (storage.Contents.Contains(user)) + return false; + + return true; + } + + private bool HasUserAccess(EntityUid uid, IEntity user, AccessReader? reader = null, bool quiet = true) { // Not having an AccessComponent means you get free access. woo! if (!Resolve(uid, ref reader)) @@ -116,12 +144,26 @@ namespace Content.Server.Lock if (!reader.IsAllowed(user)) { - reader.Owner.PopupMessage(user, Loc.GetString("lock-comp-has-user-access-fail")); + if (!quiet) + reader.Owner.PopupMessage(user, Loc.GetString("lock-comp-has-user-access-fail")); return false; } - return true; } + + private void AddToggleLockVerb(EntityUid uid, LockComponent component, GetAlternativeVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || !CanToggleLock(uid, args.User)) + return; + + Verb verb = new(); + verb.Act = component.Locked ? + () => TryUnlock(uid, args.User, component) : + () => TryLock(uid, args.User, component); + verb.Text = Loc.GetString(component.Locked ? "toggle-lock-verb-unlock" : "toggle-lock-verb-lock"); + // TODO VERB ICONS need padlock open/close icons. + args.Verbs.Add(verb); + } } } diff --git a/Content.Server/Medical/Components/MedicalScannerComponent.cs b/Content.Server/Medical/Components/MedicalScannerComponent.cs index fdca140516..49d82cd650 100644 --- a/Content.Server/Medical/Components/MedicalScannerComponent.cs +++ b/Content.Server/Medical/Components/MedicalScannerComponent.cs @@ -13,7 +13,6 @@ using Content.Shared.MedicalScanner; using Content.Shared.MobState; using Content.Shared.Popups; using Content.Shared.Preferences; -using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.GameObjects; @@ -167,51 +166,6 @@ namespace Content.Server.Medical.Components UserInterface?.Open(actor.PlayerSession); } - [Verb] - public sealed class EnterVerb : Verb - { - protected override void GetData(IEntity user, MedicalScannerComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("enter-verb-get-data-text"); - data.Visibility = component.IsOccupied ? VerbVisibility.Invisible : VerbVisibility.Visible; - } - - protected override void Activate(IEntity user, MedicalScannerComponent component) - { - component.InsertBody(user); - } - } - - [Verb] - public sealed class EjectVerb : Verb - { - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, MedicalScannerComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("medical-scanner-eject-verb-get-data-text"); - data.Visibility = component.IsOccupied ? VerbVisibility.Visible : VerbVisibility.Invisible; - data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, MedicalScannerComponent component) - { - component.EjectBody(); - } - } - public void InsertBody(IEntity user) { _bodyContainer.Insert(user); diff --git a/Content.Server/Medical/MedicalScannerSystem.cs b/Content.Server/Medical/MedicalScannerSystem.cs index 61a7d32fcf..f095b50584 100644 --- a/Content.Server/Medical/MedicalScannerSystem.cs +++ b/Content.Server/Medical/MedicalScannerSystem.cs @@ -1,9 +1,11 @@ using Content.Server.Medical.Components; using Content.Shared.ActionBlocker; +using Content.Shared.Verbs; using Content.Shared.Movement; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Timing; namespace Content.Server.Medical @@ -11,13 +13,64 @@ namespace Content.Server.Medical [UsedImplicitly] internal sealed class MedicalScannerSystem : EntitySystem { + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly ActionBlockerSystem _blocker = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnRelayMovement); + + SubscribeLocalEvent(OnRelayMovement); + SubscribeLocalEvent(AddInsertOtherVerb); + SubscribeLocalEvent(AddAlternativeVerbs); + } + + private void AddInsertOtherVerb(EntityUid uid, MedicalScannerComponent component, GetInteractionVerbsEvent args) + { + if (args.Using == null || + !args.CanAccess || + !args.CanInteract || + component.IsOccupied || + !component.CanInsert(args.Using)) + return; + + Verb verb = new(); + verb.Act = () => component.InsertBody(args.Using); + verb.Category = VerbCategory.Insert; + verb.Text = args.Using.Name; + args.Verbs.Add(verb); + } + + private void AddAlternativeVerbs(EntityUid uid, MedicalScannerComponent component, GetAlternativeVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + // Eject verb + if (component.IsOccupied) + { + Verb verb = new(); + verb.Act = () => component.EjectBody(); + verb.Category = VerbCategory.Eject; + verb.Text = Loc.GetString("medical-scanner-verb-noun-occupant"); + args.Verbs.Add(verb); + } + + // Self-insert verb + if (!component.IsOccupied && + component.CanInsert(args.User) && + _actionBlockerSystem.CanMove(args.User)) + { + Verb verb = new(); + verb.Act = () => component.InsertBody(args.User); + verb.Text = Loc.GetString("medical-scanner-verb-enter"); + // TODO VERN ICON + // TODO VERB CATEGORY + // create a verb category for "enter"? + // See also, disposal unit. Also maybe add verbs for entering lockers/body bags? + args.Verbs.Add(verb); + } } private void OnRelayMovement(EntityUid uid, MedicalScannerComponent component, RelayMovementEntityEvent args) diff --git a/Content.Server/Mind/Commands/MakeSentientCommand.cs b/Content.Server/Mind/Commands/MakeSentientCommand.cs index 0ed288df44..24bde8032d 100644 --- a/Content.Server/Mind/Commands/MakeSentientCommand.cs +++ b/Content.Server/Mind/Commands/MakeSentientCommand.cs @@ -3,6 +3,7 @@ using Content.Server.AI.Components; using Content.Server.Mind.Components; using Content.Shared.Administration; using Content.Shared.Emoting; +using Content.Shared.Examine; using Content.Shared.Movement.Components; using Content.Shared.Speech; using Robust.Shared.Console; @@ -59,6 +60,7 @@ namespace Content.Server.Mind.Commands entity.EnsureComponent(); entity.EnsureComponent(); entity.EnsureComponent(); + entity.EnsureComponent(); }); } } diff --git a/Content.Server/Mind/Verbs/ControlMobVerb.cs b/Content.Server/Mind/Verbs/ControlMobVerb.cs deleted file mode 100644 index 2520895482..0000000000 --- a/Content.Server/Mind/Verbs/ControlMobVerb.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Content.Server.Mind.Components; -using Content.Server.Players; -using Content.Shared.Verbs; -using Robust.Server.Console; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; - -namespace Content.Server.Mind.Verbs -{ - [GlobalVerb] - public class ControlMobVerb : GlobalVerb - { - public override bool RequireInteractionRange => false; - public override bool BlockedByContainers => false; - - public override void GetData(IEntity user, IEntity target, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - var groupController = IoCManager.Resolve(); - if (user == target) - { - return; - } - - if (user.TryGetComponent(out var player)) - { - if (!user.HasComponent() || !target.HasComponent()) - { - return; - } - - if (groupController.CanCommand(player.PlayerSession, "controlmob")) - { - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("control-mob-verb-get-data-text"); - data.CategoryData = VerbCategories.Debug; - } - } - } - - public override void Activate(IEntity user, IEntity target) - { - var groupController = IoCManager.Resolve(); - - var player = user.GetComponent().PlayerSession; - if (!groupController.CanCommand(player, "controlmob")) - { - return; - } - - var userMind = player.ContentData()?.Mind; - - var targetMind = target.GetComponent(); - - targetMind.Mind?.TransferTo(null); - userMind?.TransferTo(target, ghostCheckOverride: true); - } - } -} diff --git a/Content.Server/Mind/Verbs/MakeSentientVerb.cs b/Content.Server/Mind/Verbs/MakeSentientVerb.cs deleted file mode 100644 index ef8f1abcdb..0000000000 --- a/Content.Server/Mind/Verbs/MakeSentientVerb.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Content.Server.Mind.Commands; -using Content.Server.Mind.Components; -using Content.Shared.Verbs; -using Robust.Server.Console; -using Robust.Server.GameObjects; -using Robust.Shared.Console; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; - -namespace Content.Server.Mind.Verbs -{ - [GlobalVerb] - public class MakeSentientVerb : GlobalVerb - { - public override bool RequireInteractionRange => false; - public override bool BlockedByContainers => false; - - public override void GetData(IEntity user, IEntity target, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - var groupController = IoCManager.Resolve(); - - if (user == target || target.HasComponent()) - return; - - var player = user.GetComponent().PlayerSession; - if (groupController.CanCommand(player, "makesentient")) - { - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("make-sentient-verb-get-data-text"); - data.CategoryData = VerbCategories.Debug; - data.IconTexture = "/Textures/Interface/VerbIcons/sentient.svg.192dpi.png"; - } - } - - public override void Activate(IEntity user, IEntity target) - { - var groupController = IoCManager.Resolve(); - - var player = user.GetComponent().PlayerSession; - if (!groupController.CanCommand(player, "makesentient")) - return; - - var host = IoCManager.Resolve(); - var cmd = new MakeSentientCommand(); - var uidStr = target.Uid.ToString(); - cmd.Execute(new ConsoleShell(host, player), $"{cmd.Command} {uidStr}", - new[] {uidStr}); - } - } -} diff --git a/Content.Server/Morgue/Components/BodyBagEntityStorageComponent.cs b/Content.Server/Morgue/Components/BodyBagEntityStorageComponent.cs index d05d59d58e..e330e904f1 100644 --- a/Content.Server/Morgue/Components/BodyBagEntityStorageComponent.cs +++ b/Content.Server/Morgue/Components/BodyBagEntityStorageComponent.cs @@ -3,14 +3,12 @@ using Content.Server.Hands.Components; using Content.Server.Items; using Content.Server.Paper; using Content.Server.Storage.Components; -using Content.Shared.ActionBlocker; using Content.Shared.Body.Components; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Morgue; using Content.Shared.Popups; using Content.Shared.Standing; -using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.GameObjects; @@ -98,27 +96,5 @@ namespace Content.Server.Morgue.Components _appearance?.SetData(BodyBagVisuals.Label, false); } } - - - [Verb] - private sealed class RemoveLabelVerb : Verb - { - protected override void GetData(IEntity user, BodyBagEntityStorageComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user) || component.LabelContainer?.ContainedEntity == null) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("remove-label-verb-get-data-text"); - } - - /// - protected override void Activate(IEntity user, BodyBagEntityStorageComponent component) - { - component.RemoveLabel(user); - } - } } } diff --git a/Content.Server/Morgue/Components/CrematoriumEntityStorageComponent.cs b/Content.Server/Morgue/Components/CrematoriumEntityStorageComponent.cs index d44a93f672..fe6e615cfa 100644 --- a/Content.Server/Morgue/Components/CrematoriumEntityStorageComponent.cs +++ b/Content.Server/Morgue/Components/CrematoriumEntityStorageComponent.cs @@ -5,14 +5,12 @@ using Content.Server.GameTicking; using Content.Server.Players; using Content.Server.Popups; using Content.Server.Storage.Components; -using Content.Shared.ActionBlocker; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Morgue; using Content.Shared.Popups; using Content.Shared.Sound; using Content.Shared.Standing; -using Content.Shared.Verbs; using Robust.Server.Player; using Robust.Shared.Audio; using Robust.Shared.GameObjects; @@ -155,26 +153,5 @@ namespace Content.Server.Morgue.Components return SuicideKind.Heat; } - - [Verb] - private sealed class CremateVerb : Verb - { - protected override void GetData(IEntity user, CrematoriumEntityStorageComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user) || component.Cooking || component.Open) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("cremate-verb-get-data-text"); - } - - /// - protected override void Activate(IEntity user, CrematoriumEntityStorageComponent component) - { - component.TryCremate(); - } - } } } diff --git a/Content.Server/Morgue/MorgueSystem.cs b/Content.Server/Morgue/MorgueSystem.cs index 00bda44d51..142ba600c2 100644 --- a/Content.Server/Morgue/MorgueSystem.cs +++ b/Content.Server/Morgue/MorgueSystem.cs @@ -1,6 +1,8 @@ -using Content.Server.Morgue.Components; +using Content.Server.Morgue.Components; +using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.Localization; namespace Content.Server.Morgue { @@ -10,6 +12,42 @@ namespace Content.Server.Morgue private float _accumulatedFrameTime; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(AddCremateVerb); + SubscribeLocalEvent(AddRemoveLabelVerb); + } + + private void AddCremateVerb(EntityUid uid, CrematoriumEntityStorageComponent component, GetAlternativeVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || component.Cooking || component.Open) + return; + + Verb verb = new(); + verb.Text = Loc.GetString("cremate-verb-get-data-text"); + // TODO VERB ICON add flame/burn symbol? + verb.Act = () => component.TryCremate(); + args.Verbs.Add(verb); + } + + /// + /// This adds the "remove label" verb to the list of verbs. Yes, this is a stupid function name, but it's + /// consistent with other get-verb event handlers. + /// + private void AddRemoveLabelVerb(EntityUid uid, BodyBagEntityStorageComponent component, GetAlternativeVerbsEvent args) + { + if (args.Hands == null || !args.CanAccess || !args.CanInteract || component.LabelContainer?.ContainedEntity == null) + return; + + Verb verb = new(); + verb.Text = Loc.GetString("remove-label-verb-get-data-text"); + // TODO VERB ICON Add cancel/X icon? or maybe just use the pick-up or eject icon? + verb.Act = () => component.RemoveLabel(args.User); + args.Verbs.Add(verb); + } + public override void Update(float frameTime) { _accumulatedFrameTime += frameTime; diff --git a/Content.Server/PDA/PDAComponent.cs b/Content.Server/PDA/PDAComponent.cs index afc9adcc85..c6f1ac78d6 100644 --- a/Content.Server/PDA/PDAComponent.cs +++ b/Content.Server/PDA/PDAComponent.cs @@ -1,12 +1,7 @@ using System; using System.Collections.Generic; using Content.Server.Access.Components; -using Content.Shared.Containers.ItemSlots; -using Content.Shared.ActionBlocker; -using Content.Shared.PDA; -using Content.Shared.Verbs; using Robust.Shared.GameObjects; -using Robust.Shared.Localization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -52,76 +47,5 @@ namespace Content.Server.PDA throw new NotSupportedException("PDA access list is read-only."); } #endregion - - // TODO: replace me with dynamic verbs for ItemSlotsSystem - #region Verbs - [Verb] - public sealed class EjectPenVerb : Verb - { - protected override void GetData(IEntity user, PDAComponent component, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - if (!component.Owner.TryGetComponent(out SharedItemSlotsComponent? slots)) - return; - - if (!EntitySystem.Get().CanInteract(user)) - return; - - var item = EntitySystem.Get().PeekItemInSlot(slots, PenSlotName); - if (item == null) - return; - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("eject-item-verb-text-default", ("item", item.Name)); - data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, PDAComponent pda) - { - var entityManager = pda.Owner.EntityManager; - if (pda.Owner.TryGetComponent(out SharedItemSlotsComponent? itemSlots)) - { - entityManager.EntitySysManager.GetEntitySystem(). - TryEjectContent(itemSlots, PenSlotName, user); - } - } - } - - [Verb] - public sealed class EjectIDVerb : Verb - { - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, PDAComponent component, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - if (!component.Owner.TryGetComponent(out SharedItemSlotsComponent? slots)) - return; - - if (!EntitySystem.Get().CanInteract(user)) - return; - - var item = EntitySystem.Get().PeekItemInSlot(slots, IDSlotName); - if (item == null) - return; - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("eject-item-verb-text-default", ("item", item.Name)); - data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, PDAComponent pda) - { - var entityManager = pda.Owner.EntityManager; - if (pda.Owner.TryGetComponent(out SharedItemSlotsComponent? itemSlots)) - { - entityManager.EntitySysManager.GetEntitySystem(). - TryEjectContent(itemSlots, IDSlotName, user); - } - } - } - #endregion } } diff --git a/Content.Server/PDA/PDASystem.cs b/Content.Server/PDA/PDASystem.cs index cecc5ef1dc..9be9249c2d 100644 --- a/Content.Server/PDA/PDASystem.cs +++ b/Content.Server/PDA/PDASystem.cs @@ -1,5 +1,4 @@ using Content.Server.Access.Components; -using Content.Shared.Containers.ItemSlots; using Content.Server.Light.Components; using Content.Server.Light.EntitySystems; using Content.Server.Light.Events; @@ -7,6 +6,7 @@ using Content.Server.Traitor.Uplink; using Content.Server.Traitor.Uplink.Components; using Content.Server.Traitor.Uplink.Systems; using Content.Server.UserInterface; +using Content.Shared.Containers.ItemSlots; using Content.Shared.Interaction; using Content.Shared.PDA; using Robust.Server.GameObjects; @@ -24,6 +24,7 @@ namespace Content.Server.PDA public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnComponentInit); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnActivateInWorld); diff --git a/Content.Server/Pointing/EntitySystems/PointingSystem.cs b/Content.Server/Pointing/EntitySystems/PointingSystem.cs index c4f655afd5..2f1e7d13be 100644 --- a/Content.Server/Pointing/EntitySystems/PointingSystem.cs +++ b/Content.Server/Pointing/EntitySystems/PointingSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.ActionBlocker; using Content.Shared.Input; using Content.Shared.Interaction.Helpers; using Content.Shared.Popups; +using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Server.Player; @@ -185,6 +186,8 @@ namespace Content.Server.Pointing.EntitySystems { base.Initialize(); + SubscribeLocalEvent(AddPointingVerb); + _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; CommandBinds.Builder @@ -192,6 +195,26 @@ namespace Content.Server.Pointing.EntitySystems .Register(); } + private void AddPointingVerb(GetOtherVerbsEvent args) + { + if (args.Hands == null) + return; + + //Check if the object is already being pointed at + if (args.Target.HasComponent()) + return; + + if (!args.User.TryGetComponent(out var actor) || + !InRange(args.User, args.Target.Transform.Coordinates)) + return; + + Verb verb = new(); + verb.Text = Loc.GetString("pointing-verb-get-data-text"); + verb.IconTexture = "/Textures/Interface/VerbIcons/point.svg.192dpi.png"; + verb.Act = () => TryPoint(actor.PlayerSession, args.Target.Transform.Coordinates, args.Target.Uid); ; + args.Verbs.Add(verb); + } + public override void Shutdown() { base.Shutdown(); diff --git a/Content.Server/Pointing/PointingVerb.cs b/Content.Server/Pointing/PointingVerb.cs deleted file mode 100644 index 54127cbbe7..0000000000 --- a/Content.Server/Pointing/PointingVerb.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Content.Server.Pointing.Components; -using Content.Server.Pointing.EntitySystems; -using Content.Shared.Verbs; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.Localization; - -namespace Content.Server.Pointing -{ - /// - /// Global verb that points at an entity. - /// - [GlobalVerb] - public class PointingVerb : GlobalVerb - { - public override bool RequireInteractionRange => false; - - public override void GetData(IEntity user, IEntity target, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - data.IconTexture = "/Textures/Interface/VerbIcons/point.svg.192dpi.png"; - - if (!user.HasComponent()) - { - return; - } - - if (!EntitySystem.Get().InRange(user, target.Transform.Coordinates)) - { - return; - } - - if (target.HasComponent()) - { - return; - } - - data.Visibility = VerbVisibility.Visible; - - data.Text = Loc.GetString("pointing-verb-get-data-text"); - } - - public override void Activate(IEntity user, IEntity target) - { - if (!user.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - EntitySystem.Get().TryPoint(actor.PlayerSession, target.Transform.Coordinates, target.Uid); - } - } -} diff --git a/Content.Server/Power/Components/BaseCharger.cs b/Content.Server/Power/Components/BaseCharger.cs index a0eaa0c58c..813d8e83c0 100644 --- a/Content.Server/Power/Components/BaseCharger.cs +++ b/Content.Server/Power/Components/BaseCharger.cs @@ -3,11 +3,9 @@ using System.Threading.Tasks; using Content.Server.Hands.Components; using Content.Server.Items; using Content.Server.Weapon.Ranged.Barrels.Components; -using Content.Shared.ActionBlocker; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Power; -using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.GameObjects; @@ -25,7 +23,9 @@ namespace Content.Server.Power.Components private BatteryComponent? _heldBattery; [ViewVariables] - private ContainerSlot _container = default!; + public ContainerSlot Container = default!; + + public bool HasCell => Container.ContainedEntity != null; [ViewVariables] private CellChargerStatus _status; @@ -43,7 +43,7 @@ namespace Content.Server.Power.Components base.Initialize(); Owner.EnsureComponent(); - _container = ContainerHelpers.EnsureContainer(Owner, $"{Name}-powerCellContainer"); + Container = ContainerHelpers.EnsureContainer(Owner, $"{Name}-powerCellContainer"); // Default state in the visualizer is OFF, so when this gets powered on during initialization it will generally show empty } @@ -85,15 +85,15 @@ namespace Content.Server.Power.Components /// This will remove the item directly into the user's hand / floor /// /// - private void RemoveItem(IEntity user) + public void RemoveItem(IEntity user) { - var heldItem = _container.ContainedEntity; + var heldItem = Container.ContainedEntity; if (heldItem == null) { return; } - _container.Remove(heldItem); + Container.Remove(heldItem); _heldBattery = null; if (user.TryGetComponent(out HandsComponent? handsComponent)) { @@ -113,81 +113,6 @@ namespace Content.Server.Power.Components UpdateStatus(); } - [Verb] - private sealed class InsertVerb : Verb - { - protected override void GetData(IEntity user, BaseCharger component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - if (!user.TryGetComponent(out HandsComponent? handsComponent)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - if (component._container.ContainedEntity != null || handsComponent.GetActiveHand == null) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - var heldItemName = Loc.GetString(handsComponent.GetActiveHand.Owner.Name); - - data.Text = Loc.GetString("insert-verb-get-data-text", ("itemName", heldItemName)); - data.IconTexture = "/Textures/Interface/VerbIcons/insert.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, BaseCharger component) - { - if (!user.TryGetComponent(out HandsComponent? handsComponent)) - { - return; - } - - if (handsComponent.GetActiveHand == null) - { - return; - } - var userItem = handsComponent.GetActiveHand.Owner; - handsComponent.Drop(userItem); - component.TryInsertItem(userItem); - } - } - - [Verb] - private sealed class EjectVerb : Verb - { - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, BaseCharger component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - if (component._container.ContainedEntity == null) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - var containerItemName = Loc.GetString(component._container.ContainedEntity.Name); - - data.Text = Loc.GetString("eject-verb-get-data-text",("containerName", containerItemName)); - data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, BaseCharger component) - { - component.RemoveItem(user); - } - } - private CellChargerStatus GetStatus() { if (Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) && @@ -195,7 +120,7 @@ namespace Content.Server.Power.Components { return CellChargerStatus.Off; } - if (_container.ContainedEntity == null) + if (!HasCell) { return CellChargerStatus.Empty; } @@ -206,13 +131,13 @@ namespace Content.Server.Power.Components return CellChargerStatus.Charging; } - private bool TryInsertItem(IEntity entity) + public bool TryInsertItem(IEntity entity) { - if (!IsEntityCompatible(entity) || _container.ContainedEntity != null) + if (!IsEntityCompatible(entity) || HasCell) { return false; } - if (!_container.Insert(entity)) + if (!Container.Insert(entity)) { return false; } @@ -224,7 +149,7 @@ namespace Content.Server.Power.Components /// /// If the supplied entity should fit into the charger. /// - protected abstract bool IsEntityCompatible(IEntity entity); + public abstract bool IsEntityCompatible(IEntity entity); protected abstract BatteryComponent? GetBatteryFrom(IEntity entity); @@ -264,12 +189,12 @@ namespace Content.Server.Power.Components throw new ArgumentOutOfRangeException(); } - appearance?.SetData(CellVisual.Occupied, _container.ContainedEntity != null); + appearance?.SetData(CellVisual.Occupied, HasCell); } public void OnUpdate(float frameTime) //todo: make single system for this { - if (_status == CellChargerStatus.Empty || _status == CellChargerStatus.Charged || _container.ContainedEntity == null) + if (_status == CellChargerStatus.Empty || _status == CellChargerStatus.Charged || !HasCell) { return; } diff --git a/Content.Server/Power/EntitySystems/BaseChargerSystem.cs b/Content.Server/Power/EntitySystems/BaseChargerSystem.cs index bf7b5be615..f1896b49ee 100644 --- a/Content.Server/Power/EntitySystems/BaseChargerSystem.cs +++ b/Content.Server/Power/EntitySystems/BaseChargerSystem.cs @@ -1,12 +1,29 @@ using Content.Server.Power.Components; +using Content.Server.PowerCell.Components; +using Content.Server.Weapon; +using Content.Shared.ActionBlocker; +using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; namespace Content.Server.Power.EntitySystems { [UsedImplicitly] internal sealed class BaseChargerSystem : EntitySystem { + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(AddEjectVerb); + SubscribeLocalEvent(AddInsertVerb); + SubscribeLocalEvent(AddEjectVerb); + SubscribeLocalEvent(AddInsertVerb); + } + public override void Update(float frameTime) { foreach (var comp in EntityManager.EntityQuery(true)) @@ -14,5 +31,39 @@ namespace Content.Server.Power.EntitySystems comp.OnUpdate(frameTime); } } + + // TODO VERBS EJECTABLES Standardize eject/insert verbs into a single system? + private void AddEjectVerb(EntityUid uid, BaseCharger component, GetAlternativeVerbsEvent args) + { + if (args.Hands == null || + !args.CanAccess || + !args.CanInteract || + !component.HasCell || + !_actionBlockerSystem.CanPickup(args.User)) + return; + + Verb verb = new(); + verb.Text = component.Container.ContainedEntity!.Name; + verb.Category = VerbCategory.Eject; + verb.Act = () => component.RemoveItem(args.User); + args.Verbs.Add(verb); + } + + private void AddInsertVerb(EntityUid uid, BaseCharger component, GetInteractionVerbsEvent args) + { + if (args.Using == null || + !args.CanAccess || + !args.CanInteract || + component.HasCell || + !component.IsEntityCompatible(args.Using) || + !_actionBlockerSystem.CanDrop(args.User)) + return; + + Verb verb = new(); + verb.Text = args.Using.Name; + verb.Category = VerbCategory.Insert; + verb.Act = () => component.TryInsertItem(args.Using); + args.Verbs.Add(verb); + } } } diff --git a/Content.Server/PowerCell/Components/PowerCellChargerComponent.cs b/Content.Server/PowerCell/Components/PowerCellChargerComponent.cs index a8716e2540..3b5d7b78cc 100644 --- a/Content.Server/PowerCell/Components/PowerCellChargerComponent.cs +++ b/Content.Server/PowerCell/Components/PowerCellChargerComponent.cs @@ -14,7 +14,7 @@ namespace Content.Server.PowerCell.Components { public override string Name => "PowerCellCharger"; - protected override bool IsEntityCompatible(IEntity entity) + public override bool IsEntityCompatible(IEntity entity) { return entity.HasComponent(); } diff --git a/Content.Server/PowerCell/Components/PowerCellSlotComponent.cs b/Content.Server/PowerCell/Components/PowerCellSlotComponent.cs index f5b503ce91..d90757adee 100644 --- a/Content.Server/PowerCell/Components/PowerCellSlotComponent.cs +++ b/Content.Server/PowerCell/Components/PowerCellSlotComponent.cs @@ -1,12 +1,9 @@ using System; using Content.Server.Hands.Components; using Content.Server.Items; -using Content.Shared.ActionBlocker; using Content.Shared.Audio; using Content.Shared.Examine; -using Content.Shared.Interaction.Events; using Content.Shared.Sound; -using Content.Shared.Verbs; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.GameObjects; @@ -176,42 +173,6 @@ namespace Content.Server.PowerCell.Components return true; } - [Verb] - public sealed class EjectCellVerb : Verb - { - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, PowerCellSlotComponent component, VerbData data) - { - if (!component.ShowVerb || !EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - if (component.Cell == null) - { - data.Text = Loc.GetString("power-cell-slot-component-no-cell"); - data.Visibility = VerbVisibility.Disabled; - } - else - { - data.Text = Loc.GetString("power-cell-slot-component-eject-cell"); - data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - } - - if (component.Cell == null || !component.CanRemoveCell) - { - data.Visibility = VerbVisibility.Disabled; - } - } - - protected override void Activate(IEntity user, PowerCellSlotComponent component) - { - component.EjectCell(user); - } - } - void IMapInit.MapInit() { if (_startEmpty || _cellContainer.ContainedEntity != null) diff --git a/Content.Server/PowerCell/PowerCellSystem.cs b/Content.Server/PowerCell/PowerCellSystem.cs index 714a00fcbf..1be44b0545 100644 --- a/Content.Server/PowerCell/PowerCellSystem.cs +++ b/Content.Server/PowerCell/PowerCellSystem.cs @@ -1,5 +1,7 @@ -using Content.Server.PowerCell.Components; +using Content.Server.PowerCell.Components; +using Content.Shared.ActionBlocker; using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -10,12 +12,50 @@ namespace Content.Server.PowerCell public class PowerCellSystem : EntitySystem { [Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnSolutionChange); + SubscribeLocalEvent(AddEjectVerb); + SubscribeLocalEvent(AddInsertVerb); + } + + // TODO VERBS EJECTABLES Standardize eject/insert verbs into a single system? + private void AddEjectVerb(EntityUid uid, PowerCellSlotComponent component, GetAlternativeVerbsEvent args) + { + if (args.Hands == null || + !args.CanAccess || + !args.CanInteract || + !component.ShowVerb || + !component.HasCell || + !_actionBlockerSystem.CanPickup(args.User)) + return; + + Verb verb = new(); + verb.Text = component.Cell!.Name; + verb.Category = VerbCategory.Eject; + verb.Act = () => component.EjectCell(args.User); + args.Verbs.Add(verb); + } + + private void AddInsertVerb(EntityUid uid, PowerCellSlotComponent component, GetInteractionVerbsEvent args) + { + if (args.Using == null || + !args.CanAccess || + !args.CanInteract || + component.HasCell || + !args.Using.HasComponent() || + !_actionBlockerSystem.CanDrop(args.User)) + return; + + Verb verb = new(); + verb.Text = args.Using.Name; + verb.Category = VerbCategory.Insert; + verb.Act = () => component.InsertCell(args.Using); + args.Verbs.Add(verb); } private void OnSolutionChange(EntityUid uid, PowerCellComponent component, SolutionChangedEvent args) diff --git a/Content.Server/Pulling/PullableComponent.cs b/Content.Server/Pulling/PullableComponent.cs deleted file mode 100644 index 2c1d3fb767..0000000000 --- a/Content.Server/Pulling/PullableComponent.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Content.Shared.Hands.Components; -using Content.Shared.Interaction; -using Content.Shared.Pulling.Components; -using Content.Shared.Pulling; -using Content.Shared.Verbs; -using Robust.Shared.GameObjects; -using Robust.Shared.Localization; -using Robust.Shared.Physics; - -namespace Content.Server.Pulling -{ - [RegisterComponent] - [ComponentReference(typeof(SharedPullableComponent))] - public class PullableComponent : SharedPullableComponent - { - [Verb] - public class PullingVerb : Verb - { - protected override void GetData(IEntity user, PullableComponent component, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - if (user == component.Owner) - { - return; - } - - if (!user.Transform.Coordinates.TryDistance(user.EntityManager, component.Owner.Transform.Coordinates, out var distance) || - distance > SharedInteractionSystem.InteractionRange) - { - return; - } - - if (!user.HasComponent() || - !user.TryGetComponent(out IPhysBody? userPhysics) || - !component.Owner.TryGetComponent(out IPhysBody? targetPhysics) || - targetPhysics.BodyType == BodyType.Static) - { - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = component.Puller == userPhysics.Owner - ? Loc.GetString("pulling-verb-get-data-text-stop-pulling") - : Loc.GetString("pulling-verb-get-data-text"); - } - - protected override void Activate(IEntity user, PullableComponent component) - { - // There used to be sanity checks here for no reason. - // Why no reason? Because they're supposed to be performed in TryStartPull. - if (component.Puller == user) - { - EntitySystem.Get().TryStopPull(component); - } - else - { - EntitySystem.Get().TryStartPull(component.Owner, user); - } - } - } - } -} diff --git a/Content.Server/Pulling/PullingSystem.cs b/Content.Server/Pulling/PullingSystem.cs index 99e9e45aae..eb3eabe508 100644 --- a/Content.Server/Pulling/PullingSystem.cs +++ b/Content.Server/Pulling/PullingSystem.cs @@ -17,8 +17,8 @@ namespace Content.Server.Pulling UpdatesAfter.Add(typeof(PhysicsSystem)); - SubscribeLocalEvent(OnPullableMove); - SubscribeLocalEvent(OnPullableStopMove); + SubscribeLocalEvent(OnPullableMove); + SubscribeLocalEvent(OnPullableStopMove); CommandBinds.Builder .Bind(ContentKeyFunctions.ReleasePulledObject, InputCmdHandler.FromDelegate(HandleReleasePulledObject)) diff --git a/Content.Server/Rotatable/FlippableComponent.cs b/Content.Server/Rotatable/FlippableComponent.cs new file mode 100644 index 0000000000..5f561df825 --- /dev/null +++ b/Content.Server/Rotatable/FlippableComponent.cs @@ -0,0 +1,19 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Rotatable +{ + [RegisterComponent] + public class FlippableComponent : Component + { + public override string Name => "Flippable"; + + /// + /// Entity to replace this entity with when the current one is 'flipped'. + /// + [DataField("mirrorEntity", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MirrorEntity = default!; + } +} diff --git a/Content.Server/Rotatable/RotatableSystem.cs b/Content.Server/Rotatable/RotatableSystem.cs new file mode 100644 index 0000000000..7cb4dc6f17 --- /dev/null +++ b/Content.Server/Rotatable/RotatableSystem.cs @@ -0,0 +1,82 @@ +using Content.Shared.Popups; +using Content.Shared.Rotatable; +using Content.Shared.Verbs; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; +using Robust.Shared.Maths; +using Robust.Shared.Physics; + +namespace Content.Server.Rotatable +{ + /// + /// Handles verbs for the and components. + /// + public class RotatableSystem : EntitySystem + { + public override void Initialize() + { + SubscribeLocalEvent(AddFlipVerb); + SubscribeLocalEvent(AddRotateVerbs); + } + + private void AddFlipVerb(EntityUid uid, FlippableComponent component, GetOtherVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || component.MirrorEntity == null) + return; + + Verb verb = new(); + verb.Act = () => TryFlip(component, args.User); + verb.Text = Loc.GetString("flippable-verb-get-data-text"); + // TODO VERB ICONS Add Uno reverse card style icon? + args.Verbs.Add(verb); + } + + private void AddRotateVerbs(EntityUid uid, RotatableComponent component, GetOtherVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + // Check if the object is anchored, and whether we are still allowed to rotate it. + if (!component.RotateWhileAnchored && + component.Owner.TryGetComponent(out IPhysBody? physics) && + physics.BodyType == BodyType.Static) + return; + + // rotate clockwise + Verb rotateCW = new(); + rotateCW.Act = () => component.Owner.Transform.LocalRotation += Angle.FromDegrees(-90); + rotateCW.Category = VerbCategory.Rotate; + rotateCW.IconTexture = "/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png"; + rotateCW.Priority = -2; // show CCW, then CW + rotateCW.CloseMenu = false; // allow for easy double rotations. + args.Verbs.Add(rotateCW); + + // rotate counter-clockwise + Verb rotateCCW = new(); + rotateCCW.Act = () => component.Owner.Transform.LocalRotation += Angle.FromDegrees(90); + rotateCCW.Category = VerbCategory.Rotate; + rotateCCW.IconTexture = "/Textures/Interface/VerbIcons/rotate_ccw.svg.192dpi.png"; + rotateCCW.Priority = -1; + rotateCCW.CloseMenu = false; // allow for easy double rotations. + args.Verbs.Add(rotateCCW); + } + + /// + /// Replace a flippable entity with it's flipped / mirror-symmetric entity. + /// + public static void TryFlip(FlippableComponent component, IEntity user) + { + // TODO FLIPPABLE Currently an entity needs to be un-anchored when flipping. But the newly spawned entity + // defaults to being anchored (and spawns under floor tiles). Fix this? + if (component.Owner.TryGetComponent(out IPhysBody? physics) && + physics.BodyType == BodyType.Static) + { + component.Owner.PopupMessage(user, Loc.GetString("flippable-component-try-flip-is-stuck")); + return; + } + + component.Owner.EntityManager.SpawnEntity(component.MirrorEntity, component.Owner.Transform.Coordinates); + component.Owner.Delete(); + } + } +} diff --git a/Content.Server/Rotation/Components/FlippableComponent.cs b/Content.Server/Rotation/Components/FlippableComponent.cs deleted file mode 100644 index 1c4fa5f308..0000000000 --- a/Content.Server/Rotation/Components/FlippableComponent.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Content.Shared.ActionBlocker; -using Content.Shared.Popups; -using Content.Shared.Verbs; -using Robust.Shared.GameObjects; -using Robust.Shared.Localization; -using Robust.Shared.Physics; -using Robust.Shared.Serialization.Manager.Attributes; - -namespace Content.Server.Rotation.Components -{ - [RegisterComponent] - public class FlippableComponent : Component - { - public override string Name => "Flippable"; - - - private string? _entity => _internalEntity ?? Owner.Prototype?.ID; - - [DataField("entity")] - private string? _internalEntity; - - private void TryFlip(IEntity user) - { - if (Owner.TryGetComponent(out IPhysBody? physics) && - physics.BodyType == BodyType.Static) - { - Owner.PopupMessage(user, Loc.GetString("flippable-component-try-flip-is-stuck")); - return; - } - - if (_entity == null) - { - return; - } - - Owner.EntityManager.SpawnEntity(_entity, Owner.Transform.Coordinates); - Owner.Delete(); - } - - [Verb] - private sealed class FlippableVerb : Verb - { - protected override void GetData(IEntity user, FlippableComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("flippable-verb-get-data-text"); - } - - protected override void Activate(IEntity user, FlippableComponent component) - { - component.TryFlip(user); - } - } - } -} diff --git a/Content.Server/Rotation/Components/RotatableComponent.cs b/Content.Server/Rotation/Components/RotatableComponent.cs deleted file mode 100644 index 5b795741b5..0000000000 --- a/Content.Server/Rotation/Components/RotatableComponent.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Content.Shared.ActionBlocker; -using Content.Shared.Popups; -using Content.Shared.Rotatable; -using Content.Shared.Verbs; -using Robust.Shared.GameObjects; -using Robust.Shared.Localization; -using Robust.Shared.Maths; -using Robust.Shared.Physics; - -namespace Content.Server.Rotation.Components -{ - [RegisterComponent] - [ComponentReference(typeof(SharedRotatableComponent))] - public class RotatableComponent : SharedRotatableComponent - { - private void TryRotate(IEntity user, Angle angle) - { - if (!RotateWhileAnchored && Owner.TryGetComponent(out IPhysBody? physics)) - { - if (physics.BodyType == BodyType.Static) - { - Owner.PopupMessage(user, Loc.GetString("rotatable-component-try-rotate-stuck")); - return; - } - } - - Owner.Transform.LocalRotation += angle; - } - - [Verb] - public sealed class RotateVerb : Verb - { - protected override void GetData(IEntity user, RotatableComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user) || (!component.RotateWhileAnchored && component.Owner.TryGetComponent(out IPhysBody? physics) && physics.BodyType == BodyType.Static)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.CategoryData = VerbCategories.Rotate; - data.Text = Loc.GetString("rotate-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, RotatableComponent component) - { - component.TryRotate(user, Angle.FromDegrees(-90)); - } - } - - [Verb] - public sealed class RotateCounterVerb : Verb - { - protected override void GetData(IEntity user, RotatableComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user) || (!component.RotateWhileAnchored && component.Owner.TryGetComponent(out IPhysBody? physics) && physics.BodyType == BodyType.Static)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.CategoryData = VerbCategories.Rotate; - data.Text = Loc.GetString("rotate-counter-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/rotate_ccw.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, RotatableComponent component) - { - component.TryRotate(user, Angle.FromDegrees(90)); - } - } - - } -} diff --git a/Content.Server/Rotation/HardRotateVerbs.cs b/Content.Server/Rotation/HardRotateVerbs.cs deleted file mode 100644 index 608e771b55..0000000000 --- a/Content.Server/Rotation/HardRotateVerbs.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Content.Server.Rotation -{ - // Mapping tools - // Uncomment if you need them, I guess. - - /* - [GlobalVerb] - public sealed class HardRotateCcwVerb : GlobalVerb - { - public override bool RequireInteractionRange => false; - - public override void GetData(IEntity user, IEntity target, VerbData data) - { - data.Visibility = VerbVisibility.Visible; - data.Text = "Rotate CCW"; - data.IconTexture = "/Textures/Interface/VerbIcons/rotate_ccw.svg.192dpi.png"; - } - - public override void Activate(IEntity user, IEntity target) - { - target.Transform.LocalRotation += Math.PI / 2; - } - } - - [GlobalVerb] - public sealed class HardRotateCwVerb : GlobalVerb - { - public override bool RequireInteractionRange => false; - - public override void GetData(IEntity user, IEntity target, VerbData data) - { - data.Visibility = VerbVisibility.Visible; - data.Text = "Rotate CW"; - data.IconTexture = "/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png"; - } - - public override void Activate(IEntity user, IEntity target) - { - target.Transform.LocalRotation -= Math.PI / 2; - } - }*/ -} diff --git a/Content.Server/Storage/Components/EntityStorageComponent.cs b/Content.Server/Storage/Components/EntityStorageComponent.cs index fbdbe5374b..ecb8da9c18 100644 --- a/Content.Server/Storage/Components/EntityStorageComponent.cs +++ b/Content.Server/Storage/Components/EntityStorageComponent.cs @@ -76,7 +76,7 @@ namespace Content.Server.Storage.Components private SoundSpecifier _openSound = new SoundPathSpecifier("/Audio/Effects/closetopen.ogg"); [ViewVariables] - protected Container Contents = default!; + public Container Contents = default!; /// /// Determines if the container contents should be drawn when the container is closed. @@ -156,13 +156,6 @@ namespace Content.Server.Storage.Components public virtual void Activate(ActivateEventArgs eventArgs) { - // HACK until EntityStorageComponent gets refactored to the new ECS system - if (Owner.TryGetComponent(out var @lock) && @lock.Locked) - { - // Do nothing, LockSystem is responsible for handling this case - return; - } - ToggleOpen(eventArgs.User); } @@ -173,6 +166,13 @@ namespace Content.Server.Storage.Components if (!silent) Owner.PopupMessage(user, Loc.GetString("entity-storage-component-welded-shut-message")); return false; } + + if (Owner.TryGetComponent(out var @lock) && @lock.Locked) + { + if (!silent) Owner.PopupMessage(user, Loc.GetString("entity-storage-component-locked-message")); + return false; + } + return true; } @@ -181,7 +181,7 @@ namespace Content.Server.Storage.Components return true; } - private void ToggleOpen(IEntity user) + public void ToggleOpen(IEntity user) { if (Open) { @@ -422,48 +422,6 @@ namespace Content.Server.Storage.Components return entityLookup.GetEntitiesIntersecting(Owner); } - [Verb] - private sealed class OpenToggleVerb : Verb - { - protected override void GetData(IEntity user, EntityStorageComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - component.OpenVerbGetData(user, component, data); - } - - /// - protected override void Activate(IEntity user, EntityStorageComponent component) - { - component.ToggleOpen(user); - } - } - - protected virtual void OpenVerbGetData(IEntity user, EntityStorageComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user) || - component.Owner.TryGetComponent(out LockComponent? lockComponent) && lockComponent.Locked) // HACK extra check, until EntityStorage gets refactored - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - if (IsWeldedShut) - { - data.Visibility = VerbVisibility.Disabled; - var verb = Loc.GetString(component.Open ? "open-toggle-verb-close" : "open-toggle-verb-open"); - data.Text = Loc.GetString("open-toggle-verb-welded-shut-message", ("verb", verb)); - return; - } - - data.Text = Loc.GetString(component.Open ? "open-toggle-verb-close" : "open-toggle-verb-open"); - data.IconTexture = component.Open ? "/Textures/Interface/VerbIcons/close.svg.192dpi.png" : "/Textures/Interface/VerbIcons/open.svg.192dpi.png"; - } - void IExAct.OnExplosion(ExplosionEventArgs eventArgs) { if (eventArgs.Severity < ExplosionSeverity.Heavy) diff --git a/Content.Server/Storage/Components/ServerStorageComponent.cs b/Content.Server/Storage/Components/ServerStorageComponent.cs index ede22b0eba..b553f38e11 100644 --- a/Content.Server/Storage/Components/ServerStorageComponent.cs +++ b/Content.Server/Storage/Components/ServerStorageComponent.cs @@ -629,46 +629,5 @@ namespace Content.Server.Storage.Components { SoundSystem.Play(Filter.Pvs(Owner), StorageSoundCollection.GetSound(), Owner, AudioParams.Default); } - - [Verb] - private sealed class ToggleOpenVerb : Verb - { - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, ServerStorageComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - // Get the session for the user - var session = user.GetComponentOrNull()?.PlayerSession; - if (session == null) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - // Does this player currently have the storage UI open? - if (component.SubscribedSessions.Contains(session)) - { - data.Text = Loc.GetString("toggle-open-verb-close"); - data.IconTexture = "/Textures/Interface/VerbIcons/close.svg.192dpi.png"; - } else - { - data.Text = Loc.GetString("toggle-open-verb-open"); - data.IconTexture = "/Textures/Interface/VerbIcons/open.svg.192dpi.png"; - } - } - - /// - protected override void Activate(IEntity user, ServerStorageComponent component) - { - // "Open" actually closes the UI if it is already open. - component.OpenStorageUI(user); - } - } } } diff --git a/Content.Server/Storage/EntitySystems/StorageSystem.cs b/Content.Server/Storage/EntitySystems/StorageSystem.cs index 657725b81e..c5ace24ab1 100644 --- a/Content.Server/Storage/EntitySystems/StorageSystem.cs +++ b/Content.Server/Storage/EntitySystems/StorageSystem.cs @@ -2,13 +2,16 @@ using System.Collections.Generic; using Content.Server.Hands.Components; using Content.Server.Interaction; using Content.Server.Storage.Components; +using Content.Shared.Verbs; using Content.Shared.Movement; using JetBrains.Annotations; +using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Timing; +using Robust.Shared.Localization; namespace Content.Server.Storage.EntitySystems { @@ -26,6 +29,9 @@ namespace Content.Server.Storage.EntitySystems SubscribeLocalEvent(HandleEntityRemovedFromContainer); SubscribeLocalEvent(HandleEntityInsertedIntoContainer); + + SubscribeLocalEvent(AddToggleOpenVerb); + SubscribeLocalEvent(AddOpenUiVerb); SubscribeLocalEvent(OnRelayMovement); } @@ -53,6 +59,60 @@ namespace Content.Server.Storage.EntitySystems } } + private void AddToggleOpenVerb(EntityUid uid, EntityStorageComponent component, GetInteractionVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + if (!component.CanOpen(args.User, silent: true)) + return; + + Verb verb = new(); + if (component.Open) + { + verb.Text = Loc.GetString("verb-categories-close"); + verb.IconTexture = "/Textures/Interface/VerbIcons/close.svg.192dpi.png"; + } + else + { + verb.Text = Loc.GetString("verb-categories-open"); + verb.IconTexture = "/Textures/Interface/VerbIcons/open.svg.192dpi.png"; + } + verb.Act = () => component.ToggleOpen(args.User); + args.Verbs.Add(verb); + } + + private void AddOpenUiVerb(EntityUid uid, ServerStorageComponent component, GetActivationVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + if (EntityManager.TryGetComponent(uid, out LockComponent? lockComponent) && lockComponent.Locked) + return; + + // Get the session for the user + var session = args.User.GetComponentOrNull()?.PlayerSession; + if (session == null) + return; + + // Does this player currently have the storage UI open? + var uiOpen = component.SubscribedSessions.Contains(session); + + Verb verb = new(); + verb.Act = () => component.OpenStorageUI(args.User); + if (uiOpen) + { + verb.Text = Loc.GetString("verb-categories-close"); + verb.IconTexture = "/Textures/Interface/VerbIcons/close.svg.192dpi.png"; + } + else + { + verb.Text = Loc.GetString("verb-categories-open"); + verb.IconTexture = "/Textures/Interface/VerbIcons/open.svg.192dpi.png"; + } + args.Verbs.Add(verb); + } + private static void HandleEntityRemovedFromContainer(EntRemovedFromContainerMessage message) { var oldParentEntity = message.Container.Owner; diff --git a/Content.Server/Strip/StrippableComponent.cs b/Content.Server/Strip/StrippableComponent.cs index b96eabb20c..a3bcea18b4 100644 --- a/Content.Server/Strip/StrippableComponent.cs +++ b/Content.Server/Strip/StrippableComponent.cs @@ -11,7 +11,6 @@ using Content.Shared.DragDrop; using Content.Shared.Hands.Components; using Content.Shared.Popups; using Content.Shared.Strip.Components; -using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.GameObjects; @@ -145,7 +144,7 @@ namespace Content.Server.Strip return dictionary; } - private void OpenUserInterface(IPlayerSession session) + public void OpenUserInterface(IPlayerSession session) { UserInterface?.Open(session); } @@ -444,31 +443,5 @@ namespace Content.Server.Strip break; } } - - [Verb] - private sealed class StripVerb : Verb - { - protected override void GetData(IEntity user, StrippableComponent component, VerbData data) - { - if (!component.CanBeStripped(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("strip-verb-get-data-text"); - } - - protected override void Activate(IEntity user, StrippableComponent component) - { - if (!user.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - component.OpenUserInterface(actor.PlayerSession); - } - } } } diff --git a/Content.Server/Strip/StrippableSystem.cs b/Content.Server/Strip/StrippableSystem.cs new file mode 100644 index 0000000000..7941e06baf --- /dev/null +++ b/Content.Server/Strip/StrippableSystem.cs @@ -0,0 +1,32 @@ +using Content.Shared.Verbs; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; + +namespace Content.Server.Strip +{ + public sealed class StrippableSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(AddStripVerb); + } + + private void AddStripVerb(EntityUid uid, StrippableComponent component, GetOtherVerbsEvent args) + { + if (args.Hands == null || !args.CanAccess || !args.CanInteract || args.Target == args.User) + return; + + if (!args.User.TryGetComponent(out ActorComponent? actor)) + return; + + Verb verb = new(); + verb.Text = Loc.GetString("strip-verb-get-data-text"); + verb.IconTexture = "/Textures/Interface/VerbIcons/outfit.svg.192dpi.png"; + verb.Act = () => component.OpenUserInterface(actor.PlayerSession); + args.Verbs.Add(verb); + } + } +} diff --git a/Content.Server/Tabletop/Components/TabletopGameComponent.cs b/Content.Server/Tabletop/Components/TabletopGameComponent.cs index 88f5e6ea46..4a3750b5f8 100644 --- a/Content.Server/Tabletop/Components/TabletopGameComponent.cs +++ b/Content.Server/Tabletop/Components/TabletopGameComponent.cs @@ -1,9 +1,5 @@ -using Content.Shared.ActionBlocker; -using Content.Shared.Verbs; -using Robust.Server.GameObjects; using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; -using Robust.Shared.Localization; using Robust.Shared.Maths; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -32,30 +28,5 @@ namespace Content.Server.Tabletop.Components [ViewVariables] public TabletopSession? Session { get; set; } = null; - - /// - /// A verb that allows the player to start playing a tabletop game. - /// - [Verb] - public class PlayVerb : Verb - { - protected override void GetData(IEntity user, TabletopGameComponent component, VerbData data) - { - if (!user.HasComponent() || !EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("tabletop-verb-play-game"); - data.IconTexture = "/Textures/Interface/VerbIcons/die.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, TabletopGameComponent component) - { - if(user.TryGetComponent(out ActorComponent? actor)) - EntitySystem.Get().OpenSessionFor(actor.PlayerSession, component.Owner.Uid); - } - } } } diff --git a/Content.Server/Tabletop/TabletopSystem.cs b/Content.Server/Tabletop/TabletopSystem.cs index 44bf7babd2..5242865592 100644 --- a/Content.Server/Tabletop/TabletopSystem.cs +++ b/Content.Server/Tabletop/TabletopSystem.cs @@ -1,14 +1,16 @@ -using Content.Server.Tabletop.Components; +using Content.Server.Tabletop.Components; using Content.Shared.ActionBlocker; using Content.Shared.Interaction; using Content.Shared.Tabletop; using Content.Shared.Tabletop.Events; +using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Map; namespace Content.Server.Tabletop @@ -24,14 +26,32 @@ namespace Content.Server.Tabletop SubscribeNetworkEvent(OnStopPlaying); SubscribeLocalEvent(OnTabletopActivate); SubscribeLocalEvent(OnGameShutdown); - SubscribeLocalEvent(OnPlayerDetached); SubscribeLocalEvent(OnGamerShutdown); + SubscribeLocalEvent(AddPlayGameVerb); InitializeMap(); InitializeDraggable(); } + /// + /// Add a verb that allows the player to start playing a tabletop game. + /// + private void AddPlayGameVerb(EntityUid uid, TabletopGameComponent component, GetActivationVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + if (!args.User.TryGetComponent(out var actor)) + return; + + Verb verb = new(); + verb.Text = Loc.GetString("tabletop-verb-play-game"); + verb.IconTexture = "/Textures/Interface/VerbIcons/die.svg.192dpi.png"; + verb.Act = () => OpenSessionFor(actor.PlayerSession, uid); + args.Verbs.Add(verb); + } + private void OnTabletopActivate(EntityUid uid, TabletopGameComponent component, ActivateInWorldEvent args) { // Check that a player is attached to the entity. diff --git a/Content.Server/Transform/Verbs/AttachToGrandparentVerb.cs b/Content.Server/Transform/Verbs/AttachToGrandparentVerb.cs deleted file mode 100644 index ab9d3b75a0..0000000000 --- a/Content.Server/Transform/Verbs/AttachToGrandparentVerb.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Content.Shared.Transform; -using Content.Shared.Verbs; -using Robust.Server.Console; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; - -namespace Content.Server.Transform.Verbs -{ - [GlobalVerb] - public class AttachToGrandparentVerb : GlobalVerb - { - public override void GetData(IEntity user, IEntity target, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - if (user == target) - { - return; - } - - if (!user.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - var groupController = IoCManager.Resolve(); - if (!groupController.CanCommand(actor.PlayerSession, "attachtograndparent")) - { - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("attach-to-grandparent-verb-get-data-text"); - data.CategoryData = VerbCategories.Debug; - } - - public override void Activate(IEntity user, IEntity target) - { - if (!user.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - var groupController = IoCManager.Resolve(); - if (!groupController.CanCommand(actor.PlayerSession, "attachtograndparent")) - { - return; - } - - target.Transform.AttachToGrandparent(); - } - } -} diff --git a/Content.Server/Transform/Verbs/AttachToGridVerb.cs b/Content.Server/Transform/Verbs/AttachToGridVerb.cs deleted file mode 100644 index eb19efac10..0000000000 --- a/Content.Server/Transform/Verbs/AttachToGridVerb.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Content.Shared.Verbs; -using Robust.Server.Console; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; - -namespace Content.Server.Transform.Verbs -{ - [GlobalVerb] - public class AttachToGridVerb : GlobalVerb - { - public override void GetData(IEntity user, IEntity target, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - if (user == target) - { - return; - } - - if (!user.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - var groupController = IoCManager.Resolve(); - if (!groupController.CanCommand(actor.PlayerSession, "attachtogrid")) - { - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("attach-to-grid-verb-get-data-text"); - data.CategoryData = VerbCategories.Debug; - } - - public override void Activate(IEntity user, IEntity target) - { - if (!user.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - var groupController = IoCManager.Resolve(); - if (!groupController.CanCommand(actor.PlayerSession, "attachtogrid")) - { - return; - } - - target.Transform.AttachToGridOrMap(); - } - } -} diff --git a/Content.Server/Transform/Verbs/AttachToSelf.cs b/Content.Server/Transform/Verbs/AttachToSelf.cs deleted file mode 100644 index c4f224daad..0000000000 --- a/Content.Server/Transform/Verbs/AttachToSelf.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Content.Shared.Verbs; -using Robust.Server.Console; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; - -namespace Content.Server.Transform.Verbs -{ - [GlobalVerb] - public class AttachToSelf : GlobalVerb - { - public override void GetData(IEntity user, IEntity target, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - if (user == target) - { - return; - } - - if (!user.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - var groupController = IoCManager.Resolve(); - if (!groupController.CanCommand(actor.PlayerSession, "attachtoself")) - { - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("attach-to-self-verb-get-data-text"); - data.CategoryData = VerbCategories.Debug; - } - - public override void Activate(IEntity user, IEntity target) - { - if (!user.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - var groupController = IoCManager.Resolve(); - if (!groupController.CanCommand(actor.PlayerSession, "attachtoself")) - { - return; - } - - target.Transform.AttachParent(user); - } - } -} diff --git a/Content.Server/Containers/Commands/ShowContainedContextCommand.cs b/Content.Server/Verbs/ToggleAllContextCommand.cs similarity index 69% rename from Content.Server/Containers/Commands/ShowContainedContextCommand.cs rename to Content.Server/Verbs/ToggleAllContextCommand.cs index 0780861d56..630e7dda46 100644 --- a/Content.Server/Containers/Commands/ShowContainedContextCommand.cs +++ b/Content.Server/Verbs/ToggleAllContextCommand.cs @@ -8,13 +8,13 @@ using Robust.Shared.GameObjects; namespace Content.Server.Containers.Commands { [AdminCommand(AdminFlags.Debug)] - public class ShowContainedContextCommand : IConsoleCommand + public class ToggleAllContextCommand : IConsoleCommand { - public const string CommandName = "showcontainedcontext"; + public const string CommandName = "toggleallcontext"; // ReSharper disable once StringLiteralTypo public string Command => CommandName; - public string Description => "Makes contained entities visible on the context menu, even when they shouldn't be."; + public string Description => "Toggles showing all entities visible on the context menu, even when they shouldn't be."; public string Help => $"{Command}"; public void Execute(IConsoleShell shell, string argStr, string[] args) @@ -26,7 +26,7 @@ namespace Content.Server.Containers.Commands return; } - EntitySystem.Get().AddContainerVisibility(player); + EntitySystem.Get().ToggleSeeAllContext(player); } } } diff --git a/Content.Server/Verbs/VerbSystem.cs b/Content.Server/Verbs/VerbSystem.cs index 34f53def99..3286e04fbd 100644 --- a/Content.Server/Verbs/VerbSystem.cs +++ b/Content.Server/Verbs/VerbSystem.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Reflection; +using System.Collections.Generic; using Content.Shared.GameTicking; using Content.Shared.Verbs; using Robust.Server.Player; @@ -7,15 +6,17 @@ using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Log; -using static Content.Shared.Verbs.VerbSystemMessages; namespace Content.Server.Verbs { - public class VerbSystem : SharedVerbSystem + public sealed class VerbSystem : SharedVerbSystem { [Dependency] private readonly IPlayerManager _playerManager = default!; - private readonly HashSet _seesThroughContainers = new(); + /// + /// List of players that can see all entities on the context menu, ignoring normal visibility rules. + /// + public readonly HashSet SeeAllContextPlayers = new(); public override void Initialize() { @@ -24,157 +25,112 @@ namespace Content.Server.Verbs IoCManager.InjectDependencies(this); SubscribeLocalEvent(Reset); - SubscribeNetworkEvent(RequestVerbs); - SubscribeNetworkEvent(UseVerb); + SubscribeNetworkEvent(HandleVerbRequest); + SubscribeNetworkEvent(HandleTryExecuteVerb); _playerManager.PlayerStatusChanged += PlayerStatusChanged; } + public override void Shutdown() + { + base.Shutdown(); + _playerManager.PlayerStatusChanged -= PlayerStatusChanged; + } + private void PlayerStatusChanged(object? sender, SessionStatusEventArgs args) { if (args.NewStatus == SessionStatus.Disconnected) { - _seesThroughContainers.Remove(args.Session); + SeeAllContextPlayers.Remove(args.Session); } } public void Reset(RoundRestartCleanupEvent ev) { - _seesThroughContainers.Clear(); + SeeAllContextPlayers.Clear(); } - public void AddContainerVisibility(IPlayerSession session) + public void ToggleSeeAllContext(IPlayerSession player) { - if (!_seesThroughContainers.Add(session)) + if (!SeeAllContextPlayers.Add(player)) { - return; + SeeAllContextPlayers.Remove(player); } - var message = new PlayerContainerVisibilityMessage(true); - RaiseNetworkEvent(message, session.ConnectedClient); + SetSeeAllContextEvent args = new() { CanSeeAllContext = SeeAllContextPlayers.Contains(player) }; + RaiseNetworkEvent(args, player.ConnectedClient); } - public void RemoveContainerVisibility(IPlayerSession session) + /// + /// Called when asked over the network to run a given verb. + /// + public void HandleTryExecuteVerb(TryExecuteVerbEvent args, EntitySessionEventArgs eventArgs) { - if (!_seesThroughContainers.Remove(session)) - { - return; - } - - var message = new PlayerContainerVisibilityMessage(false); - RaiseNetworkEvent(message, session.ConnectedClient); - } - - public bool HasContainerVisibility(IPlayerSession session) - { - return _seesThroughContainers.Contains(session); - } - - private void UseVerb(UseVerbMessage use, EntitySessionEventArgs eventArgs) - { - if (!EntityManager.TryGetEntity(use.EntityUid, out var entity)) - { - return; - } - var session = eventArgs.SenderSession; var userEntity = session.AttachedEntity; if (userEntity == null) { - Logger.Warning($"{nameof(UseVerb)} called by player {session} with no attached entity."); + Logger.Warning($"{nameof(HandleTryExecuteVerb)} called by player {session} with no attached entity."); return; } - foreach (var (component, verb) in VerbUtility.GetVerbs(entity)) + if (!EntityManager.TryGetEntity(args.Target, out var targetEntity)) { - if ($"{component.GetType()}:{verb.GetType()}" != use.VerbKey) - { - continue; - } - - if (!VerbUtility.VerbAccessChecks(userEntity, entity, verb)) - { - break; - } - - verb.Activate(userEntity, component); - break; + return; } - foreach (var globalVerb in VerbUtility.GetGlobalVerbs(Assembly.GetExecutingAssembly())) - { - if (globalVerb.GetType().ToString() != use.VerbKey) - { - continue; - } + // Get the list of verbs. This effectively also checks that the requested verb is in fact a valid verb that + // the user can perform. In principle, this might waste time checking & preparing unrelated verbs even + // though we know precisely which one we want. However, MOST entities will only have 1 or 2 verbs of a given + // type. The one exception here is the "other" verb type, which has 3-4 verbs + all the debug verbs. So maybe + // the debug verbs should be made a separate type? + var verbs = GetVerbs(targetEntity, userEntity, args.Type)[args.Type]; - if (!VerbUtility.VerbAccessChecks(userEntity, entity, globalVerb)) - { - break; - } - - globalVerb.Activate(userEntity, entity); - break; - } + // Find the requested verb. + if (verbs.TryGetValue(args.RequestedVerb, out var verb)) + TryExecuteVerb(verb); + else + // 404 Verb not found + Logger.Warning($"{nameof(HandleTryExecuteVerb)} called by player {session} with an invalid verb: {args.RequestedVerb.Category?.Text} {args.RequestedVerb.Text}"); } - private void RequestVerbs(RequestVerbsMessage req, EntitySessionEventArgs eventArgs) + private void HandleVerbRequest(RequestServerVerbsEvent args, EntitySessionEventArgs eventArgs) { var player = (IPlayerSession) eventArgs.SenderSession; - if (!EntityManager.TryGetEntity(req.EntityUid, out var entity)) + if (!EntityManager.TryGetEntity(args.EntityUid, out var target)) { - Logger.Warning($"{nameof(RequestVerbs)} called on a nonexistant entity with id {req.EntityUid} by player {player}."); + Logger.Warning($"{nameof(HandleVerbRequest)} called on a non-existent entity with id {args.EntityUid} by player {player}."); return; } - var userEntity = player.AttachedEntity; + var user = player.AttachedEntity; - if (userEntity == null) + if (user == null) { - Logger.Warning($"{nameof(UseVerb)} called by player {player} with no attached entity."); + Logger.Warning($"{nameof(HandleVerbRequest)} called by player {player} with no attached entity."); return; } - if (!TryGetContextEntities(userEntity, entity.Transform.MapPosition, out var entities, true) || !entities.Contains(entity)) + // Validate input (check that the user can see the entity) + TryGetContextEntities(user, + target.Transform.MapPosition, + out var entities, + buffer: true, + ignoreVisibility: SeeAllContextPlayers.Contains(player)); + + VerbsResponseEvent response; + if (entities != null && entities.Contains(target)) { - return; + response = new(args.EntityUid, GetVerbs(target, user, args.Type)); + } + else + { + // Don't leave the client hanging on "Waiting for server....", send empty response. + response = new(args.EntityUid, null); } - var data = new List(); - //Get verbs, component dependent. - foreach (var (component, verb) in VerbUtility.GetVerbs(entity)) - { - if (!VerbUtility.VerbAccessChecks(userEntity, entity, verb)) - { - continue; - } - - var verbData = verb.GetData(userEntity, component); - if (verbData.IsInvisible) - continue; - - // TODO: These keys being giant strings is inefficient as hell. - data.Add(new VerbsResponseMessage.NetVerbData(verbData, $"{component.GetType()}:{verb.GetType()}")); - } - - //Get global verbs. Visible for all entities regardless of their components. - foreach (var globalVerb in VerbUtility.GetGlobalVerbs(Assembly.GetExecutingAssembly())) - { - if (!VerbUtility.VerbAccessChecks(userEntity, entity, globalVerb)) - { - continue; - } - - var verbData = globalVerb.GetData(userEntity, entity); - if (verbData.IsInvisible) - continue; - - data.Add(new VerbsResponseMessage.NetVerbData(verbData, globalVerb.GetType().ToString())); - } - - var response = new VerbsResponseMessage(data.ToArray(), req.EntityUid); RaiseNetworkEvent(response, player.ConnectedClient); } } diff --git a/Content.Server/Weapon/Ranged/Ammunition/AmmunitionSystem.cs b/Content.Server/Weapon/Ranged/Ammunition/AmmunitionSystem.cs new file mode 100644 index 0000000000..85e0db8e92 --- /dev/null +++ b/Content.Server/Weapon/Ranged/Ammunition/AmmunitionSystem.cs @@ -0,0 +1,32 @@ +using Content.Server.Weapon.Ranged.Ammunition.Components; +using Content.Shared.Verbs; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; + +namespace Content.Server.Weapon.Ranged.Ammunition +{ + public sealed class AmmunitionSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(AddDumpVerb); + } + + private void AddDumpVerb(EntityUid uid, AmmoBoxComponent component, GetAlternativeVerbsEvent args) + { + if (args.Hands == null || !args.CanAccess || !args.CanInteract) + return; + + if (component.AmmoLeft == 0) + return; + + Verb verb = new(); + verb.Text = Loc.GetString("dump-vert-get-data-text"); + verb.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; + verb.Act = () => component.EjectContents(10); + args.Verbs.Add(verb); + } + } +} diff --git a/Content.Server/Weapon/Ranged/Ammunition/Components/AmmoBoxComponent.cs b/Content.Server/Weapon/Ranged/Ammunition/Components/AmmoBoxComponent.cs index e3cb536765..8355438080 100644 --- a/Content.Server/Weapon/Ranged/Ammunition/Components/AmmoBoxComponent.cs +++ b/Content.Server/Weapon/Ranged/Ammunition/Components/AmmoBoxComponent.cs @@ -4,11 +4,9 @@ using System.Threading.Tasks; using Content.Server.Hands.Components; using Content.Server.Items; using Content.Server.Weapon.Ranged.Barrels.Components; -using Content.Shared.ActionBlocker; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Popups; -using Content.Shared.Verbs; using Content.Shared.Weapons.Ranged.Barrels.Components; using Robust.Server.GameObjects; using Robust.Shared.Containers; @@ -92,6 +90,10 @@ namespace Content.Server.Weapon.Ranged.Ammunition.Components if (_unspawnedCount > 0) { ammo = Owner.EntityManager.SpawnEntity(_fillPrototype, Owner.Transform.Coordinates); + + // when dumping from held ammo box, this detaches the spawned ammo from the player. + ammo.Transform.AttachParentToContainerOrGrid(); + _unspawnedCount--; } @@ -183,7 +185,7 @@ namespace Content.Server.Weapon.Ranged.Ammunition.Components return true; } - private void EjectContents(int count) + public void EjectContents(int count) { var ejectCount = Math.Min(count, Capacity); var ejectAmmo = new List(ejectCount); @@ -213,31 +215,6 @@ namespace Content.Server.Weapon.Ranged.Ammunition.Components return TryUse(eventArgs.User); } - - - // So if you have 200 rounds in a box and that suddenly creates 200 entities you're not having a fun time - [Verb] - private sealed class DumpVerb : Verb - { - protected override void GetData(IEntity user, AmmoBoxComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("dump-vert-get-data-text"); - data.Visibility = component.AmmoLeft > 0 ? VerbVisibility.Visible : VerbVisibility.Disabled; - data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, AmmoBoxComponent component) - { - component.EjectContents(10); - } - } - public void Examine(FormattedMessage message, bool inDetailsRange) { message.AddMarkup("\n" + Loc.GetString("ammo-box-component-on-examine-caliber-description", ("caliber", _caliber))); diff --git a/Content.Server/Weapon/Ranged/Barrels/BarrelSystem.cs b/Content.Server/Weapon/Ranged/Barrels/BarrelSystem.cs new file mode 100644 index 0000000000..3f786fb2a1 --- /dev/null +++ b/Content.Server/Weapon/Ranged/Barrels/BarrelSystem.cs @@ -0,0 +1,149 @@ +using Content.Server.Power.Components; +using Content.Server.Weapon.Ranged.Barrels.Components; +using Content.Shared.ActionBlocker; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; + +namespace Content.Server.Weapon.Ranged.Barrels +{ + public sealed class BarrelSystem : EntitySystem + { + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(AddSpinVerb); + + SubscribeLocalEvent(AddEjectCellVerb); + SubscribeLocalEvent(AddInsertCellVerb); + + SubscribeLocalEvent(AddToggleBoltVerb); + + SubscribeLocalEvent(AddMagazineInteractionVerbs); + SubscribeLocalEvent(AddEjectMagazineVerb); + } + + private void AddSpinVerb(EntityUid uid, RevolverBarrelComponent component, GetAlternativeVerbsEvent args) + { + if (args.Hands == null || !args.CanAccess || !args.CanInteract) + return; + + if (component.Capacity <= 1 || component.ShotsLeft == 0) + return; + + Verb verb = new(); + verb.Text = Loc.GetString("spin-revolver-verb-get-data-text"); + verb.IconTexture = "/Textures/Interface/VerbIcons/refresh.svg.192dpi.png"; + verb.Act = () => + { + component.Spin(); + component.Owner.PopupMessage(args.User, Loc.GetString("spin-revolver-verb-on-activate")); + }; + args.Verbs.Add(verb); + } + + private void AddToggleBoltVerb(EntityUid uid, BoltActionBarrelComponent component, GetInteractionVerbsEvent args) + { + if (args.Hands == null || + !args.CanAccess || + !args.CanInteract) + return; + + Verb verb = new(); + verb.Text = component.BoltOpen + ? Loc.GetString("close-bolt-verb-get-data-text") + : Loc.GetString("open-bolt-verb-get-data-text"); + verb.Act = () => component.BoltOpen = !component.BoltOpen; + args.Verbs.Add(verb); + } + + // TODO VERBS EJECTABLES Standardize eject/insert verbs into a single system? + // Really, why isn't this just PowerCellSlotComponent? + private void AddEjectCellVerb(EntityUid uid, ServerBatteryBarrelComponent component, GetAlternativeVerbsEvent args) + { + if (args.Hands == null || + !args.CanAccess || + !args.CanInteract || + !component.PowerCellRemovable || + component.PowerCell == null || + !_actionBlockerSystem.CanPickup(args.User)) + return; + + Verb verb = new(); + verb.Text = component.PowerCell.Owner.Name; + verb.Category = VerbCategory.Eject; + verb.Act = () => component.TryEjectCell(args.User); + args.Verbs.Add(verb); + } + + private void AddInsertCellVerb(EntityUid uid, ServerBatteryBarrelComponent component, GetInteractionVerbsEvent args) + { + if (args.Using == null || + !args.CanAccess || + !args.CanInteract || + component.PowerCell != null || + !args.Using.HasComponent() || + !_actionBlockerSystem.CanDrop(args.User)) + return; + + Verb verb = new(); + verb.Text = args.Using.Name; + verb.Category = VerbCategory.Insert; + verb.Act = () => component.TryInsertPowerCell(args.Using); + args.Verbs.Add(verb); + } + + private void AddEjectMagazineVerb(EntityUid uid, ServerMagazineBarrelComponent component, GetAlternativeVerbsEvent args) + { + if (args.Hands == null || + !args.CanAccess || + !args.CanInteract || + !component.HasMagazine || + !_actionBlockerSystem.CanPickup(args.User)) + return; + + if (component.MagNeedsOpenBolt && !component.BoltOpen) + return; + + Verb verb = new(); + verb.Text = component.MagazineContainer.ContainedEntity!.Name; + verb.Category = VerbCategory.Eject; + verb.Act = () => component.RemoveMagazine(args.User); + args.Verbs.Add(verb); + } + + private void AddMagazineInteractionVerbs(EntityUid uid, ServerMagazineBarrelComponent component, GetInteractionVerbsEvent args) + { + if (args.Hands == null || + !args.CanAccess || + !args.CanInteract) + return; + + // Toggle bolt verb + Verb toggleBolt = new(); + toggleBolt.Text = component.BoltOpen + ? Loc.GetString("close-bolt-verb-get-data-text") + : Loc.GetString("open-bolt-verb-get-data-text"); + toggleBolt.Act = () => component.BoltOpen = !component.BoltOpen; + args.Verbs.Add(toggleBolt); + + // Are we holding a mag that we can insert? + if (args.Using == null || + !component.CanInsertMagazine(args.User, args.Using) || + !_actionBlockerSystem.CanDrop(args.User)) + return; + + // Insert mag verb + Verb insert = new(); + insert.Text = args.Using.Name; + insert.Category = VerbCategory.Insert; + insert.Act = () => component.InsertMagazine(args.User, args.Using); + args.Verbs.Add(insert); + } + } +} diff --git a/Content.Server/Weapon/Ranged/Barrels/Components/BoltActionBarrelComponent.cs b/Content.Server/Weapon/Ranged/Barrels/Components/BoltActionBarrelComponent.cs index ccf9147918..f453320d04 100644 --- a/Content.Server/Weapon/Ranged/Barrels/Components/BoltActionBarrelComponent.cs +++ b/Content.Server/Weapon/Ranged/Barrels/Components/BoltActionBarrelComponent.cs @@ -1,12 +1,10 @@ using System.Collections.Generic; using System.Threading.Tasks; using Content.Server.Weapon.Ranged.Ammunition.Components; -using Content.Shared.ActionBlocker; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Sound; -using Content.Shared.Verbs; using Content.Shared.Weapons.Ranged.Barrels.Components; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -333,47 +331,5 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components message.AddMarkup("\n" + Loc.GetString("bolt-action-barrel-component-on-examine", ("caliber", _caliber))); } - - [Verb] - private sealed class OpenBoltVerb : Verb - { - protected override void GetData(IEntity user, BoltActionBarrelComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("open-bolt-verb-get-data-text"); - data.Visibility = component.BoltOpen ? VerbVisibility.Invisible : VerbVisibility.Visible; - } - - protected override void Activate(IEntity user, BoltActionBarrelComponent component) - { - component.BoltOpen = true; - } - } - - [Verb] - private sealed class CloseBoltVerb : Verb - { - protected override void GetData(IEntity user, BoltActionBarrelComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("close-bolt-verb-get-data-text"); - data.Visibility = component.BoltOpen ? VerbVisibility.Visible : VerbVisibility.Invisible; - } - - protected override void Activate(IEntity user, BoltActionBarrelComponent component) - { - component.BoltOpen = false; - } - } } } diff --git a/Content.Server/Weapon/Ranged/Barrels/Components/RevolverBarrelComponent.cs b/Content.Server/Weapon/Ranged/Barrels/Components/RevolverBarrelComponent.cs index 80497da912..07edfe1e6a 100644 --- a/Content.Server/Weapon/Ranged/Barrels/Components/RevolverBarrelComponent.cs +++ b/Content.Server/Weapon/Ranged/Barrels/Components/RevolverBarrelComponent.cs @@ -1,11 +1,9 @@ using System; using System.Threading.Tasks; using Content.Server.Weapon.Ranged.Ammunition.Components; -using Content.Shared.ActionBlocker; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Sound; -using Content.Shared.Verbs; using Content.Shared.Weapons.Ranged.Barrels.Components; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -268,34 +266,5 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components { return TryInsertBullet(eventArgs.User, eventArgs.Using); } - - [Verb] - private sealed class SpinRevolverVerb : Verb - { - protected override void GetData(IEntity user, RevolverBarrelComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("spin-revolver-verb-get-data-text"); - if (component.Capacity <= 1) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Visibility = component.ShotsLeft > 0 ? VerbVisibility.Visible : VerbVisibility.Disabled; - data.IconTexture = "/Textures/Interface/VerbIcons/refresh.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, RevolverBarrelComponent component) - { - component.Spin(); - component.Owner.PopupMessage(user, Loc.GetString("spin-revolver-verb-on-activate")); - } - } } } diff --git a/Content.Server/Weapon/Ranged/Barrels/Components/ServerBatteryBarrelComponent.cs b/Content.Server/Weapon/Ranged/Barrels/Components/ServerBatteryBarrelComponent.cs index ce037f8119..a3878c5349 100644 --- a/Content.Server/Weapon/Ranged/Barrels/Components/ServerBatteryBarrelComponent.cs +++ b/Content.Server/Weapon/Ranged/Barrels/Components/ServerBatteryBarrelComponent.cs @@ -4,18 +4,14 @@ using Content.Server.Hands.Components; using Content.Server.Items; using Content.Server.Power.Components; using Content.Server.Projectiles.Components; -using Content.Shared.ActionBlocker; using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; using Content.Shared.Sound; -using Content.Shared.Verbs; using Content.Shared.Weapons.Ranged.Barrels.Components; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; -using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Players; @@ -46,7 +42,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components [DataField("powerCellPrototype")] private string? _powerCellPrototype = default; [DataField("powerCellRemovable")] - [ViewVariables] private bool _powerCellRemovable = default; + [ViewVariables] public bool PowerCellRemovable = default; public override int ShotsLeft { @@ -226,7 +222,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components public override bool UseEntity(UseEntityEventArgs eventArgs) { - if (!_powerCellRemovable) + if (!PowerCellRemovable) { return false; } @@ -239,9 +235,9 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components return TryEjectCell(eventArgs.User); } - private bool TryEjectCell(IEntity user) + public bool TryEjectCell(IEntity user) { - if (PowerCell == null || !_powerCellRemovable) + if (PowerCell == null || !PowerCellRemovable) { return false; } @@ -278,36 +274,5 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components return TryInsertPowerCell(eventArgs.Using); } - - [Verb] - public sealed class EjectCellVerb : Verb - { - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, ServerBatteryBarrelComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user) || !component._powerCellRemovable) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - if (component.PowerCell == null) - { - data.Text = Loc.GetString("eject-cell-verb-get-data-text-no-cell"); - data.Visibility = VerbVisibility.Disabled; - } - else - { - data.Text = Loc.GetString("eject-cell-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - } - } - - protected override void Activate(IEntity user, ServerBatteryBarrelComponent component) - { - component.TryEjectCell(user); - } - } } } diff --git a/Content.Server/Weapon/Ranged/Barrels/Components/ServerMagazineBarrelComponent.cs b/Content.Server/Weapon/Ranged/Barrels/Components/ServerMagazineBarrelComponent.cs index 8bbab9eaca..f74f5ce318 100644 --- a/Content.Server/Weapon/Ranged/Barrels/Components/ServerMagazineBarrelComponent.cs +++ b/Content.Server/Weapon/Ranged/Barrels/Components/ServerMagazineBarrelComponent.cs @@ -4,12 +4,10 @@ using System.Threading.Tasks; using Content.Server.Hands.Components; using Content.Server.Items; using Content.Server.Weapon.Ranged.Ammunition.Components; -using Content.Shared.ActionBlocker; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Sound; -using Content.Shared.Verbs; using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged.Barrels.Components; using Robust.Server.GameObjects; @@ -35,8 +33,8 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components [ViewVariables] private ContainerSlot _chamberContainer = default!; - [ViewVariables] public bool HasMagazine => _magazineContainer.ContainedEntity != null; - private ContainerSlot _magazineContainer = default!; + [ViewVariables] public bool HasMagazine => MagazineContainer.ContainedEntity != null; + public ContainerSlot MagazineContainer = default!; [ViewVariables] public MagazineType MagazineTypes => _magazineTypes; [DataField("magazineTypes")] @@ -55,7 +53,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components count++; } - var magazine = _magazineContainer.ContainedEntity; + var magazine = MagazineContainer.ContainedEntity; if (magazine != null) { count += magazine.GetComponent().ShotsLeft; @@ -71,7 +69,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components { // Chamber var count = 1; - var magazine = _magazineContainer.ContainedEntity; + var magazine = MagazineContainer.ContainedEntity; if (magazine != null) { count += magazine.GetComponent().Capacity; @@ -153,7 +151,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components public override ComponentState GetComponentState(ICommonSession player) { (int, int)? count = null; - var magazine = _magazineContainer.ContainedEntity; + var magazine = MagazineContainer.ContainedEntity; if (magazine != null && magazine.TryGetComponent(out RangedMagazineComponent? rangedMagazineComponent)) { count = (rangedMagazineComponent.ShotsLeft, rangedMagazineComponent.Capacity); @@ -176,12 +174,12 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components } _chamberContainer = ContainerHelpers.EnsureContainer(Owner, $"{Name}-chamber"); - _magazineContainer = ContainerHelpers.EnsureContainer(Owner, $"{Name}-magazine", out var existing); + MagazineContainer = ContainerHelpers.EnsureContainer(Owner, $"{Name}-magazine", out var existing); if (!existing && _magFillPrototype != null) { var magEntity = Owner.EntityManager.SpawnEntity(_magFillPrototype, Owner.Transform.Coordinates); - _magazineContainer.Insert(magEntity); + MagazineContainer.Insert(magEntity); } Dirty(); } @@ -244,7 +242,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components private void UpdateAppearance() { _appearanceComponent?.SetData(BarrelBoltVisuals.BoltOpen, BoltOpen); - _appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, _magazineContainer.ContainedEntity != null); + _appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, MagazineContainer.ContainedEntity != null); _appearanceComponent?.SetData(AmmoVisuals.AmmoCount, ShotsLeft); _appearanceComponent?.SetData(AmmoVisuals.AmmoMax, Capacity); } @@ -298,7 +296,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components } // Try and pull a round from the magazine to replace the chamber if possible - var magazine = _magazineContainer.ContainedEntity; + var magazine = MagazineContainer.ContainedEntity; var nextRound = magazine?.GetComponent().TakeAmmo(); if (nextRound == null) @@ -312,7 +310,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components { SoundSystem.Play(Filter.Pvs(Owner), _soundAutoEject.GetSound(), Owner, AudioParams.Default.WithVolume(-2)); - _magazineContainer.Remove(magazine); + MagazineContainer.Remove(magazine); SendNetworkMessage(new MagazineAutoEjectMessage()); } return true; @@ -320,7 +318,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components public void RemoveMagazine(IEntity user) { - var mag = _magazineContainer.ContainedEntity; + var mag = MagazineContainer.ContainedEntity; if (mag == null) { @@ -333,7 +331,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components return; } - _magazineContainer.Remove(mag); + MagazineContainer.Remove(mag); SoundSystem.Play(Filter.Pvs(Owner), _soundMagEject.GetSound(), Owner, AudioParams.Default.WithVolume(-2)); if (user.TryGetComponent(out HandsComponent? handsComponent)) @@ -345,41 +343,59 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components UpdateAppearance(); } + public bool CanInsertMagazine(IEntity user, IEntity magazine, bool quiet = true) + { + if (!magazine.TryGetComponent(out RangedMagazineComponent? magazineComponent)) + { + return false; + } + + if ((MagazineTypes & magazineComponent.MagazineType) == 0) + { + if (!quiet) + Owner.PopupMessage(user, Loc.GetString("server-magazine-barrel-component-interact-using-wrong-magazine-type")); + return false; + } + + if (magazineComponent.Caliber != _caliber) + { + if (!quiet) + Owner.PopupMessage(user, Loc.GetString("server-magazine-barrel-component-interact-using-wrong-caliber")); + return false; + } + + if (_magNeedsOpenBolt && !BoltOpen) + { + if (!quiet) + Owner.PopupMessage(user, Loc.GetString("server-magazine-barrel-component-interact-using-bolt-closed")); + return false; + } + + if (MagazineContainer.ContainedEntity == null) + { + return true; + } + + if (!quiet) + Owner.PopupMessage(user, Loc.GetString("server-magazine-barrel-component-interact-using-already-holding-magazine")); + return false; + } + + public void InsertMagazine(IEntity user, IEntity magazine) + { + SoundSystem.Play(Filter.Pvs(Owner), _soundMagInsert.GetSound(), Owner, AudioParams.Default.WithVolume(-2)); + Owner.PopupMessage(user, Loc.GetString("server-magazine-barrel-component-interact-using-success")); + MagazineContainer.Insert(magazine); + Dirty(); + UpdateAppearance(); + } + public override async Task InteractUsing(InteractUsingEventArgs eventArgs) { - // Insert magazine - if (eventArgs.Using.TryGetComponent(out RangedMagazineComponent? magazineComponent)) + if (CanInsertMagazine(eventArgs.User, eventArgs.Using, quiet: false)) { - if ((MagazineTypes & magazineComponent.MagazineType) == 0) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("server-magazine-barrel-component-interact-using-wrong-magazine-type")); - return false; - } - - if (magazineComponent.Caliber != _caliber) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("server-magazine-barrel-component-interact-using-wrong-caliber")); - return false; - } - - if (_magNeedsOpenBolt && !BoltOpen) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("server-magazine-barrel-component-interact-using-bolt-closed")); - return false; - } - - if (_magazineContainer.ContainedEntity == null) - { - SoundSystem.Play(Filter.Pvs(Owner), _soundMagInsert.GetSound(), Owner, AudioParams.Default.WithVolume(-2)); - Owner.PopupMessage(eventArgs.User, Loc.GetString("server-magazine-barrel-component-interact-using-success")); - _magazineContainer.Insert(eventArgs.Using); - Dirty(); - UpdateAppearance(); - return true; - } - - Owner.PopupMessage(eventArgs.User, Loc.GetString("server-magazine-barrel-component-interact-using-already-holding-magazine")); - return false; + InsertMagazine(eventArgs.User, eventArgs.Using); + return true; } // Insert 1 ammo @@ -424,80 +440,6 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components message.AddMarkup("\n" + Loc.GetString("server-magazine-barrel-component-on-examine-magazine-type", ("magazineType", magazineType))); } } - - [Verb] - private sealed class EjectMagazineVerb : Verb - { - public override bool AlternativeInteraction => true; - - protected override void GetData(IEntity user, ServerMagazineBarrelComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("eject-magazine-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - if (component.MagNeedsOpenBolt) - { - data.Visibility = component.HasMagazine && component.BoltOpen - ? VerbVisibility.Visible - : VerbVisibility.Disabled; - return; - } - - data.Visibility = component.HasMagazine ? VerbVisibility.Visible : VerbVisibility.Disabled; - } - - protected override void Activate(IEntity user, ServerMagazineBarrelComponent component) - { - component.RemoveMagazine(user); - } - } - - [Verb] - private sealed class OpenBoltVerb : Verb - { - protected override void GetData(IEntity user, ServerMagazineBarrelComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("open-bolt-verb-get-data-text"); - data.Visibility = component.BoltOpen ? VerbVisibility.Invisible : VerbVisibility.Visible; - } - - protected override void Activate(IEntity user, ServerMagazineBarrelComponent component) - { - component.BoltOpen = true; - } - } - - [Verb] - private sealed class CloseBoltVerb : Verb - { - protected override void GetData(IEntity user, ServerMagazineBarrelComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("close-bolt-verb-get-data-text"); - data.Visibility = component.BoltOpen ? VerbVisibility.Visible : VerbVisibility.Invisible; - } - - protected override void Activate(IEntity user, ServerMagazineBarrelComponent component) - { - component.BoltOpen = false; - } - } } [Flags] diff --git a/Content.Server/Weapon/WeaponCapacitorChargerComponent.cs b/Content.Server/Weapon/WeaponCapacitorChargerComponent.cs index a1e0236d24..d7af61facf 100644 --- a/Content.Server/Weapon/WeaponCapacitorChargerComponent.cs +++ b/Content.Server/Weapon/WeaponCapacitorChargerComponent.cs @@ -16,7 +16,7 @@ namespace Content.Server.Weapon { public override string Name => "WeaponCapacitorCharger"; - protected override bool IsEntityCompatible(IEntity entity) + public override bool IsEntityCompatible(IEntity entity) { return entity.TryGetComponent(out ServerBatteryBarrelComponent? battery) && battery.PowerCell != null || entity.TryGetComponent(out PowerCellSlotComponent? slot) && slot.HasCell; diff --git a/Content.Server/Wieldable/Components/WieldableComponent.cs b/Content.Server/Wieldable/Components/WieldableComponent.cs index 83a2d27c18..177d0a2f33 100644 --- a/Content.Server/Wieldable/Components/WieldableComponent.cs +++ b/Content.Server/Wieldable/Components/WieldableComponent.cs @@ -1,6 +1,4 @@ -using System.ComponentModel.DataAnnotations.Schema; using Content.Shared.Sound; -using Content.Shared.Verbs; using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; using Robust.Shared.Serialization.Manager.Attributes; @@ -36,24 +34,5 @@ namespace Content.Server.Wieldable.Components [DataField("wieldTime")] public float WieldTime = 1.5f; - - [Verb] - public sealed class ToggleWieldVerb : Verb - { - protected override void GetData(IEntity user, WieldableComponent component, VerbData data) - { - data.Visibility = VerbVisibility.Visible; - data.Text = component.Wielded ? "Unwield" : "Wield"; - } - - protected override void Activate(IEntity user, WieldableComponent component) - { - if(!component.Wielded) - EntitySystem.Get().AttemptWield(component.Owner.Uid, component, user); - else - EntitySystem.Get().AttemptUnwield(component.Owner.Uid, component, user); - - } - } } } diff --git a/Content.Server/Wieldable/WieldableSystem.cs b/Content.Server/Wieldable/WieldableSystem.cs index fabb5bd06f..dbf54017a4 100644 --- a/Content.Server/Wieldable/WieldableSystem.cs +++ b/Content.Server/Wieldable/WieldableSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.DoAfter; +using Content.Server.DoAfter; using Content.Server.Hands.Components; using Content.Server.Hands.Systems; using Content.Server.Items; @@ -7,6 +7,7 @@ using Content.Server.Wieldable.Components; using Content.Shared.Hands; using Content.Shared.Interaction; using Content.Shared.Popups; +using Content.Shared.Verbs; using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -29,10 +30,29 @@ namespace Content.Server.Wieldable SubscribeLocalEvent(OnItemUnwielded); SubscribeLocalEvent(OnItemLeaveHand); SubscribeLocalEvent(OnVirtualItemDeleted); + SubscribeLocalEvent(AddToggleWieldVerb); SubscribeLocalEvent(OnMeleeHit); } + private void AddToggleWieldVerb(EntityUid uid, WieldableComponent component, GetInteractionVerbsEvent args) + { + if (args.Hands == null || !args.CanAccess || !args.CanInteract) + return; + + // TODO VERB TOOLTIPS Make CanWield or some other function return string, set as verb tooltip and disable + // verb. Or just don't add it to the list if the action is not executable. + + Verb verb = new(); + // TODO VERBS ICON + localization + verb.Text = component.Wielded ? "Unwield" : "Wield"; + verb.Act = component.Wielded + ? () => AttemptUnwield(component.Owner.Uid, component, args.User) + : () => AttemptWield(component.Owner.Uid, component, args.User); + + args.Verbs.Add(verb); + } + private void OnUseInHand(EntityUid uid, WieldableComponent component, UseInHandEvent args) { if (args.Handled) diff --git a/Content.Shared/Administration/AdminAddReagentEuiState.cs b/Content.Shared/Administration/AdminAddReagentEuiState.cs index 87231ca7a0..9ec07d041a 100644 --- a/Content.Shared/Administration/AdminAddReagentEuiState.cs +++ b/Content.Shared/Administration/AdminAddReagentEuiState.cs @@ -1,4 +1,4 @@ -using System; +using System; using Content.Shared.Chemistry; using Content.Shared.Chemistry.Reagent; using Content.Shared.Eui; diff --git a/Content.Shared/Chemistry/EntitySystems/SolutionContainerSystem.cs b/Content.Shared/Chemistry/EntitySystems/SolutionContainerSystem.cs index 851f00e44a..d55858f3cd 100644 --- a/Content.Shared/Chemistry/EntitySystems/SolutionContainerSystem.cs +++ b/Content.Shared/Chemistry/EntitySystems/SolutionContainerSystem.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Content.Shared.Chemistry.Components; diff --git a/Content.Shared/Containers/ItemSlot/SharedItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/SharedItemSlotsSystem.cs index 820bb3e175..919a620289 100644 --- a/Content.Shared/Containers/ItemSlot/SharedItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/SharedItemSlotsSystem.cs @@ -1,10 +1,12 @@ +using Content.Shared.ActionBlocker; using Content.Shared.Hands.Components; using Content.Shared.Interaction; -using Content.Shared.Item; using Content.Shared.Popups; +using Content.Shared.Verbs; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Player; @@ -12,6 +14,8 @@ namespace Content.Shared.Containers.ItemSlots { public class SharedItemSlotsSystem : EntitySystem { + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + public override void Initialize() { base.Initialize(); @@ -19,6 +23,9 @@ namespace Content.Shared.Containers.ItemSlots SubscribeLocalEvent(OnComponentInit); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnInteractUsing); + + SubscribeLocalEvent(AddEjectVerbs); + SubscribeLocalEvent(AddInsertVerbs); } private void OnComponentInit(EntityUid uid, SharedItemSlotsComponent itemSlots, ComponentInit args) @@ -55,6 +62,53 @@ namespace Content.Shared.Containers.ItemSlots } } + private void AddEjectVerbs(EntityUid uid, SharedItemSlotsComponent component, GetAlternativeVerbsEvent args) + { + if (args.Hands == null || + !args.CanAccess || + !args.CanInteract || + !_actionBlockerSystem.CanPickup(args.User)) + return; + + foreach (var (slotName, slot) in component.Slots) + { + if (slot.ContainerSlot.ContainedEntity == null) + continue; + + Verb verb = new(); + // TODO ITEMSLOTS give item slot names localization strings? + // Basically: its much nicer to have "insert ID" instead of the much longer "Eject 's ID card (assistant)" + verb.Text = slot.ContainerSlot.ContainedEntity.Name; + verb.Category = VerbCategory.Eject; + verb.Act = () => TryEjectContent(component, slotName, args.User); + + args.Verbs.Add(verb); + } + } + + private void AddInsertVerbs(EntityUid uid, SharedItemSlotsComponent component, GetInteractionVerbsEvent args) + { + if (args.Using == null || + !args.CanAccess || + !args.CanInteract || + !_actionBlockerSystem.CanDrop(args.User)) + return; + + foreach (var (slotName, slot) in component.Slots) + { + if (!CanInsertContent(args.Using, slot)) + continue; + + Verb verb = new(); + // TODO ITEMSLOTS give item slot names localization strings? + // Basically: its much nicer to have "insert ID" instead of the much longer "Insert 's ID card (assistant)" + verb.Text = args.Using.Name; + verb.Category = VerbCategory.Insert; + verb.Act = () => InsertContent(component, slot, slotName, args.Using); + args.Verbs.Add(verb); + } + } + private void OnInteractUsing(EntityUid uid, SharedItemSlotsComponent itemSlots, InteractUsingEvent args) { if (args.Handled) @@ -64,30 +118,27 @@ namespace Content.Shared.Containers.ItemSlots } /// - /// Tries to insert item in any fitting item slot from users hand + /// Tries to insert or swap an item in any fitting item slot from users hand. If a valid slot already contains an item, it will swap it out. /// /// False if failed to insert item - public bool TryInsertContent(SharedItemSlotsComponent itemSlots, IEntity item, IEntity user) + public bool TryInsertContent(SharedItemSlotsComponent itemSlots, IEntity item, IEntity user, SharedHandsComponent? hands = null) { - foreach (var pair in itemSlots.Slots) + if (!Resolve(user.Uid, ref hands)) { - var slotName = pair.Key; - var slot = pair.Value; + itemSlots.Owner.PopupMessage(user, Loc.GetString("item-slots-try-insert-no-hands")); + return false; + } + foreach (var (slotName, slot) in itemSlots.Slots) + { // check if item allowed in whitelist if (slot.Whitelist != null && !slot.Whitelist.IsValid(item)) continue; - // check if slot is empty + // check if slot does not contain the item currently being inserted??? if (slot.ContainerSlot.Contains(item)) continue; - if (!user.TryGetComponent(out SharedHandsComponent? hands)) - { - itemSlots.Owner.PopupMessage(user, Loc.GetString("item-slots-try-insert-no-hands")); - return true; - } - // get item inside container IEntity? swap = null; if (slot.ContainerSlot.ContainedEntity != null) @@ -101,13 +152,7 @@ namespace Content.Shared.Containers.ItemSlots if (swap != null) hands.TryPutInAnyHand(swap); - // insert item - slot.ContainerSlot.Insert(item); - RaiseLocalEvent(itemSlots.Owner.Uid, new ItemSlotChanged(itemSlots, slotName, slot)); - - // play sound - if (slot.InsertSound != null) - SoundSystem.Play(Filter.Pvs(itemSlots.Owner), slot.InsertSound.GetSound(), itemSlots.Owner); + InsertContent(itemSlots, slot, slotName, item); return true; } @@ -115,6 +160,32 @@ namespace Content.Shared.Containers.ItemSlots return false; } + public void InsertContent(SharedItemSlotsComponent itemSlots, ItemSlot slot, string slotName, IEntity item) + { + // insert item + slot.ContainerSlot.Insert(item); + RaiseLocalEvent(itemSlots.Owner.Uid, new ItemSlotChanged(itemSlots, slotName, slot)); + + // play sound + if (slot.InsertSound != null) + SoundSystem.Play(Filter.Pvs(itemSlots.Owner), slot.InsertSound.GetSound(), itemSlots.Owner); + } + + /// + /// Can a given item be inserted into a slot, without ejecting the current item in that slot. + /// + public bool CanInsertContent(IEntity item, ItemSlot slot) + { + if (slot.ContainerSlot.ContainedEntity != null) + return false; + + // check if item allowed in whitelist + if (slot.Whitelist != null && !slot.Whitelist.IsValid(item)) + return false; + + return true; + } + /// /// Tries to insert item in known slot. Doesn't interact with user /// @@ -124,15 +195,10 @@ namespace Content.Shared.Containers.ItemSlots if (!itemSlots.Slots.TryGetValue(slotName, out var slot)) return false; - if (slot.ContainerSlot.ContainedEntity != null) + if (!CanInsertContent(item, slot)) return false; - // check if item allowed in whitelist - if (slot.Whitelist != null && !slot.Whitelist.IsValid(item)) - return false; - - slot.ContainerSlot.Insert(item); - RaiseLocalEvent(itemSlots.Owner.Uid, new ItemSlotChanged(itemSlots, slotName, slot)); + InsertContent(itemSlots, slot, slotName, item); return true; } diff --git a/Content.Shared/Item/SharedItemComponent.cs b/Content.Shared/Item/SharedItemComponent.cs index 50782c35b3..675fc84e07 100644 --- a/Content.Shared/Item/SharedItemComponent.cs +++ b/Content.Shared/Item/SharedItemComponent.cs @@ -109,7 +109,7 @@ namespace Content.Shared.Item /// /// If a player can pick up this item. /// - public bool CanPickup(IEntity user) + public bool CanPickup(IEntity user, bool popup = true) { if (!EntitySystem.Get().CanPickup(user)) return false; @@ -120,7 +120,7 @@ namespace Content.Shared.Item if (!Owner.TryGetComponent(out IPhysBody? physics) || physics.BodyType == BodyType.Static) return false; - return user.InRangeUnobstructed(Owner, ignoreInsideBlocker: true, popup: true); + return user.InRangeUnobstructed(Owner, ignoreInsideBlocker: true, popup: popup); } void IEquipped.Equipped(EquippedEventArgs eventArgs) diff --git a/Content.Shared/Light/Component/SharedExpendableLightComponent.cs b/Content.Shared/Light/Component/SharedExpendableLightComponent.cs index 5d8b6dca52..149041f179 100644 --- a/Content.Shared/Light/Component/SharedExpendableLightComponent.cs +++ b/Content.Shared/Light/Component/SharedExpendableLightComponent.cs @@ -34,7 +34,7 @@ namespace Content.Shared.Light.Component public sealed override string Name => "ExpendableLight"; [ViewVariables(VVAccess.ReadOnly)] - protected ExpendableLightState CurrentState { get; set; } + public ExpendableLightState CurrentState { get; set; } [ViewVariables] [DataField("turnOnBehaviourID")] diff --git a/Content.Shared/MedicalScanner/SharedMedicalScannerComponent.cs b/Content.Shared/MedicalScanner/SharedMedicalScannerComponent.cs index aa9aa2d1a3..a358aa8605 100644 --- a/Content.Shared/MedicalScanner/SharedMedicalScannerComponent.cs +++ b/Content.Shared/MedicalScanner/SharedMedicalScannerComponent.cs @@ -77,10 +77,14 @@ namespace Content.Shared.MedicalScanner } } + public bool CanInsert(IEntity entity) + { + return entity.HasComponent(); + } bool IDragDropOn.CanDragDropOn(DragDropEvent eventArgs) { - return eventArgs.Dragged.HasComponent(); + return CanInsert(eventArgs.Dragged); } public abstract bool DragDropOn(DragDropEvent eventArgs); diff --git a/Content.Shared/Pulling/Components/SharedPullableComponent.cs b/Content.Shared/Pulling/Components/PullableComponent.cs similarity index 97% rename from Content.Shared/Pulling/Components/SharedPullableComponent.cs rename to Content.Shared/Pulling/Components/PullableComponent.cs index 0d967044e5..0463ce12a4 100644 --- a/Content.Shared/Pulling/Components/SharedPullableComponent.cs +++ b/Content.Shared/Pulling/Components/PullableComponent.cs @@ -16,7 +16,8 @@ namespace Content.Shared.Pulling.Components // Before you try to add another type than SharedPullingStateManagementSystem, consider the can of worms you may be opening! [NetworkedComponent()] [Friend(typeof(SharedPullingStateManagementSystem))] - public abstract class SharedPullableComponent : Component + [RegisterComponent] + public class SharedPullableComponent : Component { public override string Name => "Pullable"; diff --git a/Content.Shared/Pulling/Systems/SharedPullerSystem.cs b/Content.Shared/Pulling/Systems/SharedPullerSystem.cs index e7e3ff0ec5..207e83174f 100644 --- a/Content.Shared/Pulling/Systems/SharedPullerSystem.cs +++ b/Content.Shared/Pulling/Systems/SharedPullerSystem.cs @@ -1,4 +1,4 @@ -using Content.Shared.Alert; +using Content.Shared.Alert; using Content.Shared.Hands; using Content.Shared.Movement.Components; using Content.Shared.Physics.Pull; diff --git a/Content.Shared/Pulling/Systems/SharedPullingSystem.cs b/Content.Shared/Pulling/Systems/SharedPullingSystem.cs index 0d09c5ac82..79663d8d8e 100644 --- a/Content.Shared/Pulling/Systems/SharedPullingSystem.cs +++ b/Content.Shared/Pulling/Systems/SharedPullingSystem.cs @@ -17,6 +17,8 @@ using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Players; using Robust.Shared.IoC; +using Content.Shared.Verbs; +using Robust.Shared.Localization; namespace Content.Shared.Pulling { @@ -63,11 +65,39 @@ namespace Content.Shared.Pulling SubscribeLocalEvent(PullableHandlePullStarted); SubscribeLocalEvent(PullableHandlePullStopped); + SubscribeLocalEvent(AddPullVerbs); + CommandBinds.Builder .Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(HandleMovePulledObject)) .Register(); } + private void AddPullVerbs(EntityUid uid, SharedPullableComponent component, GetOtherVerbsEvent args) + { + if (args.Hands == null || !args.CanAccess || !args.CanInteract) + return; + + // Are they trying to pull themselves up by their bootstraps? + if (args.User == args.Target) + return; + + //TODO VERB ICONS add pulling icon + if (component.Puller == args.User) + { + Verb verb = new(); + verb.Text = Loc.GetString("pulling-verb-get-data-text-stop-pulling"); + verb.Act = () => TryStopPull(component, args.User); + args.Verbs.Add(verb); + } + else if (CanPull(args.User, args.Target)) + { + Verb verb = new(); + verb.Text = Loc.GetString("pulling-verb-get-data-text"); + verb.Act = () => TryStartPull(args.User, args.Target); + args.Verbs.Add(verb); + } + } + // Raise a "you are being pulled" alert if the pulled entity has alerts. private static void PullableHandlePullStarted(EntityUid uid, SharedPullableComponent component, PullStartedMessage args) { @@ -227,7 +257,7 @@ namespace Content.Shared.Pulling private void UpdatePulledRotation(IEntity puller, IEntity pulled) { // TODO: update once ComponentReference works with directed event bus. - if (!pulled.TryGetComponent(out SharedRotatableComponent? rotatable)) + if (!pulled.TryGetComponent(out RotatableComponent? rotatable)) return; if (!rotatable.RotateWhilePulling) diff --git a/Content.Shared/Rotatable/SharedRotatableComponent.cs b/Content.Shared/Rotatable/RotatableComponent.cs similarity index 91% rename from Content.Shared/Rotatable/SharedRotatableComponent.cs rename to Content.Shared/Rotatable/RotatableComponent.cs index b8754ea856..6f4b091e95 100644 --- a/Content.Shared/Rotatable/SharedRotatableComponent.cs +++ b/Content.Shared/Rotatable/RotatableComponent.cs @@ -4,7 +4,8 @@ using Robust.Shared.ViewVariables; namespace Content.Shared.Rotatable { - public abstract class SharedRotatableComponent : Component + [RegisterComponent] + public class RotatableComponent : Component { public override string Name => "Rotatable"; diff --git a/Content.Shared/Verbs/GlobalVerb.cs b/Content.Shared/Verbs/GlobalVerb.cs deleted file mode 100644 index 84e8a74913..0000000000 --- a/Content.Shared/Verbs/GlobalVerb.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using JetBrains.Annotations; -using Robust.Shared.GameObjects; - -namespace Content.Shared.Verbs -{ - /// - /// A verb is an action in the right click menu of an entity. - /// Global verbs are visible on all entities, regardless of their components. - /// - /// - /// To add a global verb to all entities, - /// define it and mark it with - /// - public abstract class GlobalVerb : VerbBase - { - /// - /// Gets the visible verb data for the user. - /// - /// - /// Implementations should write into to return their data. - /// - /// The entity of the user opening this menu. - /// The entity this verb is being evaluated for. - /// The data that must be filled in. - /// The text string that is shown in the right click menu for this verb. - public abstract void GetData(IEntity user, IEntity target, VerbData data); - - /// - /// Invoked when this verb is activated from the right click menu. - /// - /// The entity of the user opening this menu. - /// The entity that is being acted upon. - public abstract void Activate(IEntity user, IEntity target); - - public VerbData GetData(IEntity user, IEntity target) - { - var data = new VerbData(); - GetData(user, target, data); - return data; - } - } - - /// - /// This attribute should be used on . These are verbs which are on visible for all entities, - /// regardless of the components they contain. - /// - [MeansImplicitUse] - [BaseTypeRequired(typeof(GlobalVerb))] - [AttributeUsage(AttributeTargets.Class, Inherited = false)] - public sealed class GlobalVerbAttribute : Attribute - { - } -} diff --git a/Content.Shared/Verbs/HideContextMenuComponent.cs b/Content.Shared/Verbs/HideContextMenuComponent.cs deleted file mode 100644 index bebccac2ec..0000000000 --- a/Content.Shared/Verbs/HideContextMenuComponent.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Robust.Shared.GameObjects; - -namespace Content.Shared.Verbs -{ - [RegisterComponent] - public class HideContextMenuComponent : Component, IShowContextMenu - { - public override string Name => "HideContextMenu"; - - public bool ShowContextMenu(IEntity examiner) - { - return false; - } - } -} diff --git a/Content.Shared/Verbs/IShowContextMenu.cs b/Content.Shared/Verbs/IShowContextMenu.cs deleted file mode 100644 index 3839ceb49d..0000000000 --- a/Content.Shared/Verbs/IShowContextMenu.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Robust.Shared.GameObjects; - -namespace Content.Shared.Verbs -{ - public interface IShowContextMenu : IComponent - { - bool ShowContextMenu(IEntity examiner); - } -} diff --git a/Content.Shared/Verbs/PlayerContainerVisibilityMessage.cs b/Content.Shared/Verbs/PlayerContainerVisibilityMessage.cs deleted file mode 100644 index 1b5ed824c7..0000000000 --- a/Content.Shared/Verbs/PlayerContainerVisibilityMessage.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization; - -namespace Content.Shared.Verbs -{ - [Serializable, NetSerializable] - public class PlayerContainerVisibilityMessage : EntityEventArgs - { - public readonly bool CanSeeThrough; - - public PlayerContainerVisibilityMessage(bool canSeeThrough) - { - CanSeeThrough = canSeeThrough; - } - } -} diff --git a/Content.Shared/Verbs/SharedVerbSystem.cs b/Content.Shared/Verbs/SharedVerbSystem.cs index 07ac5b317e..10269e1bb7 100644 --- a/Content.Shared/Verbs/SharedVerbSystem.cs +++ b/Content.Shared/Verbs/SharedVerbSystem.cs @@ -1,8 +1,9 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using Content.Shared.Interaction; -using Content.Shared.Physics; +using Content.Shared.Examine; +using Content.Shared.Interaction.Helpers; +using Content.Shared.Tag; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; @@ -15,18 +16,20 @@ namespace Content.Shared.Verbs [Dependency] private readonly IEntityLookup _lookup = default!; /// - /// Get all of the entities relevant for the contextmenu + /// Get all of the entities in an area for displaying on the context menu. /// - /// - /// - /// - /// Whether we should slightly extend out the ignored range for the ray predicated - /// - public bool TryGetContextEntities(IEntity player, MapCoordinates targetPos, [NotNullWhen(true)] out List? contextEntities, bool buffer = false) + /// Whether we should slightly extend the entity search area. + public bool TryGetContextEntities(IEntity player, MapCoordinates targetPos, + [NotNullWhen(true)] out List? contextEntities, bool buffer = false, bool ignoreVisibility = false) { contextEntities = null; - var length = buffer ? 1.0f: 0.5f; + // Check if we have LOS to the clicked-location. + if (!ignoreVisibility && !player.InRangeUnOccluded(targetPos, range: ExamineSystemShared.ExamineRange)) + return false; + + // Get entities + var length = buffer ? 1.0f : 0.5f; var entities = _lookup.GetEntitiesIntersecting( targetPos.MapId, Box2.CenteredAround(targetPos.Position, (length, length))) @@ -34,34 +37,113 @@ namespace Content.Shared.Verbs if (entities.Count == 0) return false; - // TODO: Can probably do a faster distance check with EntityCoordinates given we don't need to get map stuff. + if (ignoreVisibility) + { + contextEntities = entities; + return true; + } - // Check if we have LOS to the clicked-location, otherwise no popup. + // perform visibility checks var playerPos = player.Transform.MapPosition; - var vectorDiff = playerPos.Position - targetPos.Position; - var distance = vectorDiff.Length + 0.01f; - - bool Ignored(IEntity entity) + foreach (var entity in entities.ToList()) { - return entities.Contains(entity) || - entity == player || - !entity.TryGetComponent(out OccluderComponent? occluder) || - !occluder.Enabled; + if (entity.HasTag("HideContextMenu")) + { + entities.Remove(entity); + continue; + } + + if (!ExamineSystemShared.InRangeUnOccluded( + playerPos, + entity.Transform.MapPosition, + ExamineSystemShared.ExamineRange, + null) ) + { + entities.Remove(entity); + } } - var mask = player.TryGetComponent(out SharedEyeComponent? eye) && eye.DrawFov - ? CollisionGroup.Opaque - : CollisionGroup.None; - - var result = Get().InRangeUnobstructed(playerPos, targetPos, distance, mask, Ignored); - - if (!result) - { + if (entities.Count == 0) return false; - } contextEntities = entities; return true; } + + /// + /// Raises a number of events in order to get all verbs of the given type(s) + /// + public Dictionary> GetVerbs(IEntity target, IEntity user, VerbType verbTypes) + { + Dictionary> verbs = new(); + + if ((verbTypes & VerbType.Interaction) == VerbType.Interaction) + { + GetInteractionVerbsEvent getVerbEvent = new(user, target); + RaiseLocalEvent(target.Uid, getVerbEvent); + verbs.Add(VerbType.Interaction, getVerbEvent.Verbs); + } + + if ((verbTypes & VerbType.Activation) == VerbType.Activation) + { + GetActivationVerbsEvent getVerbEvent = new(user, target); + RaiseLocalEvent(target.Uid, getVerbEvent); + verbs.Add(VerbType.Activation, getVerbEvent.Verbs); + } + + if ((verbTypes & VerbType.Alternative) == VerbType.Alternative) + { + GetAlternativeVerbsEvent getVerbEvent = new(user, target); + RaiseLocalEvent(target.Uid, getVerbEvent); + verbs.Add(VerbType.Alternative, getVerbEvent.Verbs); + } + + if ((verbTypes & VerbType.Other) == VerbType.Other) + { + GetOtherVerbsEvent getVerbEvent = new(user, target); + RaiseLocalEvent(target.Uid, getVerbEvent); + verbs.Add(VerbType.Other, getVerbEvent.Verbs); + } + + return verbs; + } + + /// + /// Execute actions associated with the given verb. + /// + /// + /// This will try to call delegates and raise any events for the given verb. + /// + public bool TryExecuteVerb(Verb verb) + { + var executed = false; + + // Maybe run a delegate + if (verb.Act != null) + { + executed = true; + verb.Act.Invoke(); + } + + // Maybe raise a local event + if (verb.LocalVerbEventArgs != null) + { + executed = true; + if (verb.LocalEventTarget.IsValid()) + RaiseLocalEvent(verb.LocalEventTarget, verb.LocalVerbEventArgs); + else + RaiseLocalEvent(verb.LocalVerbEventArgs); + } + + // maybe raise a network event + if (verb.NetworkVerbEventArgs != null) + { + executed = true; + RaiseNetworkEvent(verb.NetworkVerbEventArgs); + } + + // return false if all of these were null + return executed; + } } } diff --git a/Content.Shared/Verbs/Verb.cs b/Content.Shared/Verbs/Verb.cs index 41cb116d86..7660ea38d0 100644 --- a/Content.Shared/Verbs/Verb.cs +++ b/Content.Shared/Verbs/Verb.cs @@ -1,90 +1,166 @@ -using System; -using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; +using System; namespace Content.Shared.Verbs { - /// - /// A verb is an action in the right click menu of an entity. - /// - /// - /// To add a verb to an entity, define it as a nested class inside the owning component, - /// and mark it with - /// - [UsedImplicitly] - public abstract class Verb : VerbBase + [Flags] + public enum VerbType { - /// - /// Gets the visible verb data for the user. - /// - /// - /// Implementations should write into to return their data. - /// - /// The entity of the user opening this menu. - /// The component instance for which this verb is being loaded. - /// The data that must be filled into. - protected abstract void GetData(IEntity user, IComponent component, VerbData data); - - /// - /// Invoked when this verb is activated from the right click menu. - /// - /// The entity of the user opening this menu. - /// The component instance for which this verb is being loaded. - public abstract void Activate(IEntity user, IComponent component); - - public VerbData GetData(IEntity user, IComponent component) - { - var data = new VerbData(); - GetData(user, component, data); - return data; - } - } - - /// - /// - /// Sub class of that works on a specific type of component, - /// to reduce casting boiler plate for implementations. - /// - /// The type of component that this verb will run on. - public abstract class Verb : Verb where T : IComponent - { - /// - /// Gets the visible verb data for the user. - /// - /// - /// Implementations should write into to return their data. - /// - /// The entity of the user opening this menu. - /// The component instance for which this verb is being loaded. - /// The data that must be filled into. - protected abstract void GetData(IEntity user, T component, VerbData data); - - /// - /// Invoked when this verb is activated from the right click menu. - /// - /// The entity of the user opening this menu. - /// The component instance for which this verb is being loaded. - protected abstract void Activate(IEntity user, T component); - - protected sealed override void GetData(IEntity user, IComponent component, VerbData data) - { - GetData(user, (T) component, data); - } - - /// - public sealed override void Activate(IEntity user, IComponent component) - { - Activate(user, (T) component); - } + Interaction = 1, + Activation = 2, + Alternative = 4, + Other = 8, + All = 1+2+4+8 } /// - /// This attribute should be used on implementations nested inside component classes, - /// so that they're automatically detected. + /// Verb objects describe actions that a user can take. The actions can be specified via an Action, local + /// events, or networked events. Verbs also provide text, icons, and categories for displaying in the + /// context-menu. /// - [AttributeUsage(AttributeTargets.Class, Inherited = false)] - [MeansImplicitUse] - public sealed class VerbAttribute : Attribute + [Serializable, NetSerializable] + public sealed class Verb : IComparable { + /// + /// This is an action that will be run when the verb is "acted" out. + /// + /// + /// This delegate probably just points to some function in the system assembling this verb. This delegate + /// will be run regardless of whether or + /// are defined. + /// + [NonSerialized] + public Action? Act; + + /// + /// This is local event that will be raised when the verb is executed. + /// + /// + /// This event will be raised regardless of whether or + /// are defined. + /// + [NonSerialized] + public object? LocalVerbEventArgs; + + /// + /// Where do direct the local event. + /// + [NonSerialized] + public EntityUid LocalEventTarget = EntityUid.Invalid; + + /// + /// This is networked event that will be raised when the verb is executed. + /// + /// + /// This event will be raised regardless of whether or + /// are defined. + /// + [NonSerialized] + public EntityEventArgs? NetworkVerbEventArgs; + + /// + /// The text that the user sees on the verb button. + /// + public string Text = string.Empty; + + /// + /// Sprite of the icon that the user sees on the verb button. + /// + public SpriteSpecifier? Icon + { + get => _icon ??= + IconTexture == null ? null : new SpriteSpecifier.Texture(new ResourcePath(IconTexture)); + set => _icon = value; + } + private SpriteSpecifier? _icon; + + /// + /// Name of the category this button is under. Used to group verbs in the context menu. + /// + public VerbCategory? Category; + + /// + /// Whether this verb is disabled. + /// + /// + /// Disabled verbs are shown in the context menu with a slightly darker background color, and cannot be + /// executed. It is recommended that a message be provided outlining why this verb is + /// disabled. + /// + public bool Disabled; + + /// + /// Optional tooltip to show when hovering over this verb. + /// + /// + /// Useful for disabled verbs as a replacement for informative pop-up messages. + /// + public string? Tooltip; + + /// + /// Determines the priority of the verb. This affects both how the verb is displayed in the context menu + /// GUI, and which verb is actually executed when left/alt clicking. + /// + /// + /// Bigger is higher priority (appears first, gets executed preferentially). + /// + public int Priority; + + /// + /// Raw texture path used to load the . + /// + public string? IconTexture; + + /// + /// Whether or not to close the context menu after using it to run this verb. + /// + /// + /// Setting this to false may be useful for repeatable actions, like rotating an object or maybe knocking on + /// a window. + /// + public bool CloseMenu = true; + + /// + /// Compares two verbs based on their , , , + /// and . + /// + /// + /// + /// This is comparison is used when storing verbs in a SortedSet. The ordering of verbs determines both how + /// the verbs are displayed in the context menu, and the order in which alternative action verbs are + /// executed when alt-clicking. + /// + /// + /// If two verbs are equal according to this comparison, they cannot both be added to the same sorted set of + /// verbs. This is desirable, given that these verbs would also appear identical in the context menu. + /// Distinct verbs should always have a unique and descriptive combination of text, icon, and category. + /// + /// + public int CompareTo(object? obj) + { + if (obj is not Verb otherVerb) + return -1; + + // Sort first by priority + if (Priority != otherVerb.Priority) + return otherVerb.Priority - Priority; + + // Then try use alphabetical verb categories. Uncategorized verbs always appear first. + if (Category?.Text != otherVerb.Category?.Text) + { + return string.Compare(Category?.Text, otherVerb.Category?.Text, StringComparison.CurrentCulture); + } + + // Then try use alphabetical verb text. + if (Text != otherVerb.Text) + { + return string.Compare(Text, otherVerb.Text, StringComparison.CurrentCulture); + } + + // Finally, compare icon texture paths. Note that this matters for verbs that don't have any text (e.g., the rotate-verbs) + return string.Compare(IconTexture, otherVerb.IconTexture, StringComparison.CurrentCulture); + } } } diff --git a/Content.Shared/Verbs/VerbBase.cs b/Content.Shared/Verbs/VerbBase.cs deleted file mode 100644 index 830b349da6..0000000000 --- a/Content.Shared/Verbs/VerbBase.cs +++ /dev/null @@ -1,24 +0,0 @@ - -namespace Content.Shared.Verbs -{ - public abstract class VerbBase - { - /// - /// If true, this verb requires the user to be inside within - /// meters from the entity on which this verb resides. - /// - public virtual bool RequireInteractionRange => true; - - /// - /// If true, this verb requires both the user and the entity on which - /// this verb resides to be in the same container or no container. - /// OR the user can be the entity's container - /// - public virtual bool BlockedByContainers => true; - - /// - /// If true, this verb can be activated by alt-clicking on the entity. - /// - public virtual bool AlternativeInteraction => false; - } -} diff --git a/Content.Shared/Verbs/VerbCategories.cs b/Content.Shared/Verbs/VerbCategories.cs deleted file mode 100644 index 8a0cbed568..0000000000 --- a/Content.Shared/Verbs/VerbCategories.cs +++ /dev/null @@ -1,18 +0,0 @@ - -namespace Content.Shared.Verbs -{ - /// - /// Standard verb categories. - /// - public static class VerbCategories - { - public static readonly VerbCategoryData Debug = - ("Debug", "/Textures/Interface/VerbIcons/debug.svg.192dpi.png"); - - public static readonly VerbCategoryData Rotate = ("Rotate", null); - public static readonly VerbCategoryData Construction = - ("Construction", "/Textures/Interface/hammer_scaled.svg.192dpi.png"); - public static readonly VerbCategoryData SetTransferAmount = - ("Set Transfer Amount", "/Textures/Interface/VerbIcons/spill.svg.192dpi.png"); - } -} diff --git a/Content.Shared/Verbs/VerbCategory.cs b/Content.Shared/Verbs/VerbCategory.cs new file mode 100644 index 0000000000..2de3eb145f --- /dev/null +++ b/Content.Shared/Verbs/VerbCategory.cs @@ -0,0 +1,54 @@ +using Robust.Shared.Localization; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; +using System; + +namespace Content.Shared.Verbs +{ + /// + /// Contains combined name and icon information for a verb category. + /// + [Serializable, NetSerializable] + public class VerbCategory + { + public readonly string Text; + + public readonly SpriteSpecifier? Icon; + + /// + /// If true, this verb category is shown in the context menu as a row of icons without any text. + /// + /// + /// For example, the 'Rotate' category simply shows two icons for rotating left and right. + /// + public readonly bool IconsOnly; + + public VerbCategory(string text, string? icon, bool iconsOnly = false) + { + Text = Loc.GetString(text); + Icon = icon == null ? null : new SpriteSpecifier.Texture(new ResourcePath(icon)); + IconsOnly = iconsOnly; + } + + public static readonly VerbCategory Debug = + new("verb-categories-debug", "/Textures/Interface/VerbIcons/debug.svg.192dpi.png"); + + public static readonly VerbCategory Eject = + new("verb-categories-eject", "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"); + + public static readonly VerbCategory Insert = + new("verb-categories-insert", "/Textures/Interface/VerbIcons/insert.svg.192dpi.png"); + + public static readonly VerbCategory Buckle = + new("verb-categories-buckle", "/Textures/Interface/VerbIcons/buckle.svg.192dpi.png"); + + public static readonly VerbCategory Unbuckle = + new("verb-categories-unbuckle", "/Textures/Interface/VerbIcons/unbuckle.svg.192dpi.png"); + + public static readonly VerbCategory Rotate = + new("verb-categories-rotate", "/Textures/Interface/VerbIcons/refresh.svg.192dpi.png", iconsOnly: true); + + public static readonly VerbCategory SetTransferAmount = + new("verb-categories-transfer", "/Textures/Interface/VerbIcons/spill.svg.192dpi.png"); + } +} diff --git a/Content.Shared/Verbs/VerbCategoryData.cs b/Content.Shared/Verbs/VerbCategoryData.cs deleted file mode 100644 index b0ee545159..0000000000 --- a/Content.Shared/Verbs/VerbCategoryData.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Robust.Shared.Utility; - -namespace Content.Shared.Verbs -{ - /// - /// Contains combined name and icon information for a verb category. - /// - public readonly struct VerbCategoryData - { - public VerbCategoryData(string name, SpriteSpecifier? icon) - { - Name = name; - Icon = icon; - } - - public string Name { get; } - public SpriteSpecifier? Icon { get; } - - public static implicit operator VerbCategoryData((string name, string? icon) tuple) - { - var (name, icon) = tuple; - - if (icon == null) - { - return new VerbCategoryData(name, null); - } - - return new VerbCategoryData(name, new SpriteSpecifier.Texture(new ResourcePath(icon))); - } - } -} diff --git a/Content.Shared/Verbs/VerbData.cs b/Content.Shared/Verbs/VerbData.cs deleted file mode 100644 index b76c278a2e..0000000000 --- a/Content.Shared/Verbs/VerbData.cs +++ /dev/null @@ -1,68 +0,0 @@ -using JetBrains.Annotations; -using Robust.Shared.GameObjects; -using Robust.Shared.Utility; - -namespace Content.Shared.Verbs -{ - /// - /// Stores visual data for a verb. - /// - /// - /// An instance of this class gets instantiated by the verb system and should be filled in by implementations of - /// . - /// - public sealed class VerbData - { - /// - /// The text that the user sees on the verb button. - /// - /// - /// This string is automatically passed through Loc.GetString(). - /// - public string Text { get; set; } = string.Empty; - - /// - /// Sprite of the icon that the user sees on the verb button. - /// - public SpriteSpecifier? Icon { get; set; } - - /// - /// Name of the category this button is under. - /// - public string Category { get; set; } = ""; - - /// - /// Sprite of the icon that the user sees on the verb button. - /// - public SpriteSpecifier? CategoryIcon { get; set; } - - /// - /// Whether this verb is visible, disabled (greyed out) or hidden. - /// - public VerbVisibility Visibility { get; set; } = VerbVisibility.Visible; - - public bool IsInvisible => Visibility == VerbVisibility.Invisible; - public bool IsDisabled => Visibility == VerbVisibility.Disabled; - - /// - /// Convenience property to set verb category and icon at once. - /// - [ValueProvider("Content.Shared.GameObjects.VerbCategories")] - public VerbCategoryData CategoryData - { - set - { - Category = value.Name; - CategoryIcon = value.Icon; - } - } - - /// - /// Convenience property to set to a raw texture path. - /// - public string IconTexture - { - set => Icon = new SpriteSpecifier.Texture(new ResourcePath(value)); - } - } -} diff --git a/Content.Shared/Verbs/VerbEvents.cs b/Content.Shared/Verbs/VerbEvents.cs new file mode 100644 index 0000000000..4b1175aace --- /dev/null +++ b/Content.Shared/Verbs/VerbEvents.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using Content.Shared.ActionBlocker; +using Content.Shared.Hands.Components; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using Robust.Shared.IoC; +using Content.Shared.Interaction; + +namespace Content.Shared.Verbs +{ + [Serializable, NetSerializable] + public class RequestServerVerbsEvent : EntityEventArgs + { + public readonly EntityUid EntityUid; + public readonly VerbType Type; + + public RequestServerVerbsEvent(EntityUid entityUid, VerbType type) + { + EntityUid = entityUid; + Type = type; + } + } + + [Serializable, NetSerializable] + public class VerbsResponseEvent : EntityEventArgs + { + public readonly Dictionary>? Verbs; + public readonly EntityUid Entity; + + public VerbsResponseEvent(EntityUid entity, Dictionary>? verbs) + { + Entity = entity; + + if (verbs == null) + return; + + // Apparently SortedSet is not serlializable. Cast to List. + Verbs = new(); + foreach (var entry in verbs) + { + Verbs.Add(entry.Key, new List(entry.Value)); + } + } + } + + [Serializable, NetSerializable] + public class TryExecuteVerbEvent : EntityEventArgs + { + public readonly EntityUid Target; + public readonly Verb RequestedVerb; + + /// + /// The type of verb to try execute. Avoids having to get a list of all verbs on the receiving end. + /// + public readonly VerbType Type; + + public TryExecuteVerbEvent(EntityUid target, Verb requestedVerb, VerbType type) + { + Target = target; + RequestedVerb = requestedVerb; + Type = type; + } + } + + /// + /// Event used to toggle visibility of all context menu entities. + /// + [Serializable, NetSerializable] + public class SetSeeAllContextEvent : EntityEventArgs + { + public bool CanSeeAllContext = false; + } + + /// + /// Request primary interaction verbs. This includes both use-in-hand and interacting with external entities. + /// + /// + /// These verbs those that involve using the hands or the currently held item on some entity. These verbs usually + /// correspond to interactions that can be triggered by left-clicking or using 'Z', and often depend on the + /// currently held item. These verbs are collectively shown first in the context menu. + /// + public class GetInteractionVerbsEvent : GetVerbsEvent + { + public GetInteractionVerbsEvent(IEntity user, IEntity target) : base(user, target) { } + } + + /// + /// Request activation verbs. + /// + /// + /// These are verbs that activate an item in the world but are independent of the currently held items. For + /// example, opening a door or a GUI. These verbs should correspond to interactions that can be triggered by + /// using 'E', though many of those can also be triggered by left-mouse or 'Z' if there is no other interaction. + /// These verbs are collectively shown second in the context menu. + /// + public class GetActivationVerbsEvent : GetVerbsEvent + { + public GetActivationVerbsEvent(IEntity user, IEntity target) : base(user, target) { } + } + + /// + /// Request alternative-interaction verbs. + /// + /// + /// When interacting with an entity via alt + left-click/E/Z the highest priority alt-interact verb is executed. + /// These verbs are collectively shown second-to-last in the context menu. + /// + public class GetAlternativeVerbsEvent : GetVerbsEvent + { + public GetAlternativeVerbsEvent(IEntity user, IEntity target) : base(user, target) { } + } + + /// + /// Request Miscellaneous verbs. + /// + /// + /// Includes (nearly) global interactions like "examine", "pull", or "debug". These verbs are collectively shown + /// last in the context menu. + /// + public class GetOtherVerbsEvent : GetVerbsEvent + { + public GetOtherVerbsEvent(IEntity user, IEntity target) : base(user, target) { } + } + + /// + /// Directed event that requests verbs from any systems/components on a target entity. + /// + public class GetVerbsEvent : EntityEventArgs + { + /// + /// Event output. Set of verbs that can be executed. + /// + public SortedSet Verbs = new(); + + /// + /// Can the user physically access the target? + /// + /// + /// This is a combination of and + /// . + /// + public bool CanAccess; + + /// + /// The entity being targeted for the verb. + /// + public IEntity Target; + + /// + /// The entity that will be "performing" the verb. + /// + public IEntity User; + + /// + /// Can the user physically interact? + /// + /// + /// This is a just a cached result. Given that many verbs need + /// to check this, it prevents it from having to be repeatedly called by each individual system that might + /// contribute a verb. + /// + public bool CanInteract; + + /// + /// The User's hand component. + /// + /// + /// This may be null if the user has no hands. + /// + public SharedHandsComponent? Hands; + + /// + /// The entity currently being held by the active hand. + /// + /// + /// This is only ever not null when is true and the user + /// has hands. + /// + public IEntity? Using; + + public GetVerbsEvent(IEntity user, IEntity target) + { + User = user; + Target = target; + + CanAccess = (Target == User) || user.IsInSameOrParentContainer(target) && + EntitySystem.Get().InRangeUnobstructed(user, target); + + // A large number of verbs need to check action blockers. Instead of repeatedly having each system individually + // call ActionBlocker checks, just cache it for the verb request. + var actionBlockerSystem = EntitySystem.Get(); + CanInteract = actionBlockerSystem.CanInteract(user); + + if (!user.TryGetComponent(out Hands) || + !actionBlockerSystem.CanUse(user)) + return; + + Hands.TryGetActiveHeldEntity(out Using); + + // Check whether the "Held" entity is a virtual pull entity. If yes, set that as the entity being "Used". + // This allows you to do things like buckle a dragged person onto a surgery table, without click-dragging + // their sprite. + if (Using != null && Using.TryGetComponent(out var pull)) + { + Using = IoCManager.Resolve().GetEntity(pull.BlockingEntity); + } + } + } +} diff --git a/Content.Shared/Verbs/VerbSystemMessages.cs b/Content.Shared/Verbs/VerbSystemMessages.cs deleted file mode 100644 index 7452e8440b..0000000000 --- a/Content.Shared/Verbs/VerbSystemMessages.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization; -using Robust.Shared.Utility; - -namespace Content.Shared.Verbs -{ - public static class VerbSystemMessages - { - [Serializable, NetSerializable] - public class RequestVerbsMessage : EntityEventArgs - { - public readonly EntityUid EntityUid; - - public RequestVerbsMessage(EntityUid entityUid) - { - EntityUid = entityUid; - } - } - - [Serializable, NetSerializable] - public class VerbsResponseMessage : EntityEventArgs - { - public readonly NetVerbData[] Verbs; - public readonly EntityUid Entity; - - public VerbsResponseMessage(NetVerbData[] verbs, EntityUid entity) - { - Verbs = verbs; - Entity = entity; - } - - [Serializable, NetSerializable] - public readonly struct NetVerbData - { - public readonly string Text; - public readonly string Key; - public readonly string Category; - public readonly SpriteSpecifier? Icon; - public readonly SpriteSpecifier? CategoryIcon; - public readonly bool Available; - - public NetVerbData(VerbData data, string key) - { - Text = data.Text; - Key = key; - Category = data.Category; - CategoryIcon = data.CategoryIcon; - Icon = data.Icon; - Available = data.Visibility == VerbVisibility.Visible; - } - } - } - - [Serializable, NetSerializable] - public class UseVerbMessage : EntityEventArgs - { - public readonly EntityUid EntityUid; - public readonly string VerbKey; - - public UseVerbMessage(EntityUid entityUid, string verbKey) - { - EntityUid = entityUid; - VerbKey = verbKey; - } - } - - } -} diff --git a/Content.Shared/Verbs/VerbUtility.cs b/Content.Shared/Verbs/VerbUtility.cs deleted file mode 100644 index 08289ff5e6..0000000000 --- a/Content.Shared/Verbs/VerbUtility.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Utility; - -namespace Content.Shared.Verbs -{ - public static class VerbUtility - { - public const float InteractionRange = 2; - public const float InteractionRangeSquared = InteractionRange * InteractionRange; - - // TODO: This is a quick hack. Verb objects should absolutely be cached properly. - // This works for now though. - public static IEnumerable<(IComponent, Verb)> GetVerbs(IEntity entity) - { - var typeFactory = IoCManager.Resolve(); - - foreach (var component in entity.GetAllComponents()) - { - var type = component.GetType(); - foreach (var nestedType in type.GetAllNestedTypes()) - { - if (!typeof(Verb).IsAssignableFrom(nestedType) || nestedType.IsAbstract) - { - continue; - } - - var verb = typeFactory.CreateInstance(nestedType); - yield return (component, verb); - } - } - } - - /// - /// Returns an IEnumerable of all classes inheriting with the attribute. - /// - /// The assembly to search for global verbs in. - public static IEnumerable GetGlobalVerbs(Assembly assembly) - { - var typeFactory = IoCManager.Resolve(); - - foreach (Type type in assembly.GetTypes()) - { - if (Attribute.IsDefined(type, typeof(GlobalVerbAttribute))) - { - if (!typeof(GlobalVerb).IsAssignableFrom(type) || type.IsAbstract) - { - continue; - } - yield return typeFactory.CreateInstance(type); - } - } - } - - public static bool VerbAccessChecks(IEntity user, IEntity target, VerbBase verb) - { - if (verb.RequireInteractionRange && !InVerbUseRange(user, target)) - { - return false; - } - - if (verb.BlockedByContainers && !VerbContainerCheck(user, target)) - { - return false; - } - - return true; - } - - public static bool InVerbUseRange(IEntity user, IEntity target) - { - var distanceSquared = (user.Transform.WorldPosition - target.Transform.WorldPosition) - .LengthSquared; - if (distanceSquared > InteractionRangeSquared) - { - return false; - } - return true; - } - - public static bool VerbContainerCheck(IEntity user, IEntity target) - { - if (!user.IsInSameOrNoContainer(target)) - { - if (!target.TryGetContainer(out var container) || - container.Owner != user) - { - return false; - } - } - - return true; - } - } -} diff --git a/Content.Shared/Verbs/VerbVisibility.cs b/Content.Shared/Verbs/VerbVisibility.cs deleted file mode 100644 index dadf19dd58..0000000000 --- a/Content.Shared/Verbs/VerbVisibility.cs +++ /dev/null @@ -1,24 +0,0 @@ - -namespace Content.Shared.Verbs -{ - /// - /// Possible states of visibility for the verb in the right click menu. - /// - public enum VerbVisibility - { - /// - /// The verb will be listed in the right click menu. - /// - Visible, - - /// - /// The verb will be listed, but it will be grayed out and unable to be clicked on. - /// - Disabled, - - /// - /// The verb will not be listed in the right click menu. - /// - Invisible - } -} diff --git a/Resources/Locale/en-US/access/components/id-card-console-component.ftl b/Resources/Locale/en-US/access/components/id-card-console-component.ftl index 17b147e168..a8c16d0713 100644 --- a/Resources/Locale/en-US/access/components/id-card-console-component.ftl +++ b/Resources/Locale/en-US/access/components/id-card-console-component.ftl @@ -9,10 +9,6 @@ id-card-console-window-insert-button = Insert access-id-card-console-component-no-hands-error = You have no hands. access-id-card-console-component-cannot-let-go-error = You can't let go of the ID card! -## EjectPrivilegedIDVerb - -access-eject-privileged-id-verb-get-data-text = Eject Privileged ID - -## EjectTargetIDVert - -access-eject-target-id-verb-get-data-text = Eject Target ID \ No newline at end of file +## For Verbs +id-card-console-privileged-id = Privileged ID +id-card-console-target-id = Target ID \ No newline at end of file diff --git a/Resources/Locale/en-US/body/components/bodypart-component.ftl b/Resources/Locale/en-US/body/components/bodypart-component.ftl index c72ac51c09..03e6e1e10d 100644 --- a/Resources/Locale/en-US/body/components/bodypart-component.ftl +++ b/Resources/Locale/en-US/body/components/bodypart-component.ftl @@ -4,6 +4,3 @@ bodypart-component-no-way-to-install-message = You see no way to install the {$p bodypart-component-no-way-to-attach-message = You see no useful way to attach the {$partName} anymore. bodypart-component-attach-success-message = You attach {$partName} bodypart-component-attach-fail-message = You can't attach {$partName} - -# AttachBodypartVerb -attach-bodypart-verb-get-data-text = Attach Body Part \ No newline at end of file diff --git a/Resources/Locale/en-US/buckle/components/buckle-component.ftl b/Resources/Locale/en-US/buckle/components/buckle-component.ftl index 898d7d69ee..673fcb3454 100644 --- a/Resources/Locale/en-US/buckle/components/buckle-component.ftl +++ b/Resources/Locale/en-US/buckle/components/buckle-component.ftl @@ -6,6 +6,3 @@ buckle-component-cannot-buckle-message = You can't buckle yourself there! buckle-component-other-cannot-buckle-message = You can't buckle {$owner} there! buckle-component-cannot-fit-message = You can't fit there! buckle-component-other-cannot-fit-message = {$owner} can't fit there! - -# BuckleVerb -buckle-verb-unbuckle = Unbuckle \ No newline at end of file diff --git a/Resources/Locale/en-US/buckle/components/strap-component.ftl b/Resources/Locale/en-US/buckle/components/strap-component.ftl deleted file mode 100644 index dcb56fc77e..0000000000 --- a/Resources/Locale/en-US/buckle/components/strap-component.ftl +++ /dev/null @@ -1,4 +0,0 @@ -## StrapVerb - -strap-verb-get-data-text-buckle = Buckle -strap-verb-get-data-text-unbuckle = Unbuckle \ No newline at end of file diff --git a/Resources/Locale/en-US/cabinet/item-cabinet-system.ftl b/Resources/Locale/en-US/cabinet/item-cabinet-system.ftl index 4671a4d6ba..16d7fa5e6a 100644 --- a/Resources/Locale/en-US/cabinet/item-cabinet-system.ftl +++ b/Resources/Locale/en-US/cabinet/item-cabinet-system.ftl @@ -3,11 +3,3 @@ ## Displayed when the item is successfully taken out of the cabinet. comp-item-cabinet-successfully-taken = You take { THE($item) } from { THE($cabinet) }. - -## Displayed in the context menu for the item cabinet. - -comp-item-cabinet-eject-verb-text = Eject item - -comp-item-cabinet-open-verb-text = Open -comp-item-cabinet-close-verb-text = Close - diff --git a/Resources/Locale/en-US/chemistry/components/chem-master-component.ftl b/Resources/Locale/en-US/chemistry/components/chem-master-component.ftl index c52cf23f7a..9cd25b3fc4 100644 --- a/Resources/Locale/en-US/chemistry/components/chem-master-component.ftl +++ b/Resources/Locale/en-US/chemistry/components/chem-master-component.ftl @@ -29,6 +29,3 @@ chem-master-window-create-pill-button = Create chem-master-window-create-bottle-button = Create cham-master-window-bottles-label = Bottles: chem-master-window-unknown-reagent-text = Unknown reagent - -## Eject beaker verb -eject-beaker-verb-get-data-text = Eject Beaker \ No newline at end of file diff --git a/Resources/Locale/en-US/chemistry/components/solution-transfer-component.ftl b/Resources/Locale/en-US/chemistry/components/solution-transfer-component.ftl index b40c8b3f37..14cec95129 100644 --- a/Resources/Locale/en-US/chemistry/components/solution-transfer-component.ftl +++ b/Resources/Locale/en-US/chemistry/components/solution-transfer-component.ftl @@ -9,10 +9,8 @@ comp-solution-transfer-is-empty = {THE($target)} is empty! comp-solution-transfer-is-full = {THE($target)} is full! ## Displayed in change transfer amount verb's name -comp-solution-transfer-verb-transfer-amount-min = {$amount}u -comp-solution-transfer-verb-transfer-amount-max = {$amount}u -comp-solution-transfer-verb-transfer-amount-ideal = {$amount}u -comp-solution-transfer-verb-transfer-amount-custom = Custom +comp-solution-transfer-verb-custom-amount = Custom +comp-solution-transfer-verb-amount = {$amount}u ## Displayed after you successfully change a solution's amount using the BUI comp-solution-transfer-set-amount = Transfer amount set to {$amount}u. diff --git a/Resources/Locale/en-US/construction/components/construction-component-verbs.ftl b/Resources/Locale/en-US/construction/components/construction-component-verbs.ftl index b26309fdae..d884ddc376 100644 --- a/Resources/Locale/en-US/construction/components/construction-component-verbs.ftl +++ b/Resources/Locale/en-US/construction/components/construction-component-verbs.ftl @@ -1,3 +1,3 @@ -deconstructible-verb-get-data-text = Begin deconstructing +deconstructible-verb-begin-deconstruct = Begin deconstructing deconstructible-verb-activate-no-target-text = There is no way to deconstruct this. deconstructible-verb-activate-text = Examine to see instructions. \ No newline at end of file diff --git a/Resources/Locale/en-US/disposal/mailing/components/disposal-mailing-unit-component.ftl b/Resources/Locale/en-US/disposal/mailing/components/disposal-mailing-unit-component.ftl index c47c55725e..3f6d8d449b 100644 --- a/Resources/Locale/en-US/disposal/mailing/components/disposal-mailing-unit-component.ftl +++ b/Resources/Locale/en-US/disposal/mailing/components/disposal-mailing-unit-component.ftl @@ -22,6 +22,4 @@ disposal-self-insert-verb-get-data-text = Jump inside disposal-flush-verb-get-data-text = Flush -## EjectVerb - -disposal-eject-verb-get-data-text = Eject +disposal-eject-verb-contents = contents diff --git a/Resources/Locale/en-US/examine/examine-system.ftl b/Resources/Locale/en-US/examine/examine-system.ftl index 43c90faa7d..0337e9646c 100644 --- a/Resources/Locale/en-US/examine/examine-system.ftl +++ b/Resources/Locale/en-US/examine/examine-system.ftl @@ -1,3 +1,5 @@ ## ExamineSystem -examine-system-entity-does-not-exist = That entity doesn't exist \ No newline at end of file +examine-system-entity-does-not-exist = That entity doesn't exist + +examine-verb-name = Examine \ No newline at end of file diff --git a/Resources/Locale/en-US/examine/examine-verb.ftl b/Resources/Locale/en-US/examine/examine-verb.ftl deleted file mode 100644 index 8f5424f12a..0000000000 --- a/Resources/Locale/en-US/examine/examine-verb.ftl +++ /dev/null @@ -1 +0,0 @@ -examine-verb-name = Examine \ No newline at end of file diff --git a/Resources/Locale/en-US/items/components/item-component.ftl b/Resources/Locale/en-US/items/components/item-component.ftl index 23a5375e2e..26512b5c38 100644 --- a/Resources/Locale/en-US/items/components/item-component.ftl +++ b/Resources/Locale/en-US/items/components/item-component.ftl @@ -1,3 +1,7 @@ ## PickUpVerb -pick-up-verb-get-data-text = Pick Up \ No newline at end of file +pick-up-verb-get-data-text = Pick Up + +# "pick up" doesn't make sense if the item is already in their inventory + +pick-up-verb-get-data-text-inventory = Put in hand diff --git a/Resources/Locale/en-US/light/components/expendable-light-component.ftl b/Resources/Locale/en-US/light/components/expendable-light-component.ftl new file mode 100644 index 0000000000..8a242d5525 --- /dev/null +++ b/Resources/Locale/en-US/light/components/expendable-light-component.ftl @@ -0,0 +1 @@ +expendable-light-start-verb = Start Light \ No newline at end of file diff --git a/Resources/Locale/en-US/light/components/handheld-light-component.ftl b/Resources/Locale/en-US/light/components/handheld-light-component.ftl index 3ee5dcc0f3..ba726d2227 100644 --- a/Resources/Locale/en-US/light/components/handheld-light-component.ftl +++ b/Resources/Locale/en-US/light/components/handheld-light-component.ftl @@ -2,6 +2,3 @@ handheld-light-component-on-examine-is-on-message = The light is currently [colo handheld-light-component-on-examine-is-off-message = The light is currently [color=darkred]off[/color]. handheld-light-component-cell-missing-message = Cell missing... handheld-light-component-cell-dead-message = Dead cell... - -# ToggleLightVerb -toggle-light-verb-get-data-text = Toggle light diff --git a/Resources/Locale/en-US/medical/components/medical-scanner-component.ftl b/Resources/Locale/en-US/medical/components/medical-scanner-component.ftl index 11190183fd..8ecd815057 100644 --- a/Resources/Locale/en-US/medical/components/medical-scanner-component.ftl +++ b/Resources/Locale/en-US/medical/components/medical-scanner-component.ftl @@ -5,11 +5,8 @@ medical-scanner-component-msg-soul-broken = ERROR: Soul present, but defunct / d ## EnterVerb -enter-verb-get-data-text = Enter - -## EjectVerb - -medical-scanner-eject-verb-get-data-text = Eject +medical-scanner-verb-enter = Enter +medical-scanner-verb-noun-occupant = occupant ## UI diff --git a/Resources/Locale/en-US/power-cell/components/power-cell-slot-component.ftl b/Resources/Locale/en-US/power-cell/components/power-cell-slot-component.ftl index 80bb8f71a5..d7547b653f 100644 --- a/Resources/Locale/en-US/power-cell/components/power-cell-slot-component.ftl +++ b/Resources/Locale/en-US/power-cell/components/power-cell-slot-component.ftl @@ -1,5 +1,3 @@ -power-cell-slot-component-no-cell = No cell -power-cell-slot-component-eject-cell = Eject cell power-cell-slot-component-small-size-shorthand = S power-cell-slot-component-medium-size-shorthand = M power-cell-slot-component-large-size-shorthand = L \ No newline at end of file diff --git a/Resources/Locale/en-US/power/components/power-receiver-component.ftl b/Resources/Locale/en-US/power/components/power-receiver-component.ftl index a3d81c4013..2ba4fe84b4 100644 --- a/Resources/Locale/en-US/power/components/power-receiver-component.ftl +++ b/Resources/Locale/en-US/power/components/power-receiver-component.ftl @@ -1,11 +1,3 @@ power-receiver-component-on-examine-main = It appears to be {$stateText}. power-receiver-component-on-examine-powered = [color=darkgreen]powered[/color] power-receiver-component-on-examine-unpowered = [color=darkred]un-powered[/color] - -## InsertVerb - -insert-verb-get-data-text = Insert {$itemName} - -## EjectVerb - -eject-verb-get-data-text = Eject {$containerName} \ No newline at end of file diff --git a/Resources/Locale/en-US/storage/components/entity-storage-component.ftl b/Resources/Locale/en-US/storage/components/entity-storage-component.ftl index 0d96080690..312c7e1d82 100644 --- a/Resources/Locale/en-US/storage/components/entity-storage-component.ftl +++ b/Resources/Locale/en-US/storage/components/entity-storage-component.ftl @@ -1,8 +1,7 @@ entity-storage-component-welded-shut-message = It's welded completely shut! +entity-storage-component-locked-message = It's Locked! entity-storage-component-already-contains-user-message = It's too Cramped! ## OpenToggleVerb -open-toggle-verb-close = Close -open-toggle-verb-open = Open open-toggle-verb-welded-shut-message = {$verb} (welded shut) \ No newline at end of file diff --git a/Resources/Locale/en-US/storage/components/storage-component.ftl b/Resources/Locale/en-US/storage/components/storage-component.ftl deleted file mode 100644 index e93e55363c..0000000000 --- a/Resources/Locale/en-US/storage/components/storage-component.ftl +++ /dev/null @@ -1,5 +0,0 @@ - -## ToggleOpenVerb - -toggle-open-verb-open = Open -toggle-open-verb-close = Close \ No newline at end of file diff --git a/Resources/Locale/en-US/verbs/verb-system.ftl b/Resources/Locale/en-US/verbs/verb-system.ftl index 7265ee1e2d..809b9a60e7 100644 --- a/Resources/Locale/en-US/verbs/verb-system.ftl +++ b/Resources/Locale/en-US/verbs/verb-system.ftl @@ -1,2 +1,23 @@ verb-system-waiting-on-server-text = Waiting on Server... -verb-system-no-verbs-text = No verbs! \ No newline at end of file +verb-system-no-verbs-text = No verbs! +verb-system-null-server-response = Entity not in view. You should not see this. + + +# if verbs have a range of targets (e.g., multiple ejectables) you can +# usually just use Entity.Name. But it is a bit odd when targeting yourself +verb-self-target-pronoun = Yourself + + +# verb categories & common verbs. These appear across multiple systems, so they may as well go here. + +verb-toggle-light = Toggle light + +verb-categories-debug = Debug +verb-categories-eject = Eject +verb-categories-insert = Insert +verb-categories-buckle = Buckle +verb-categories-unbuckle = Unbuckle +verb-categories-close = Close +verb-categories-open = Open +verb-categories-rotate = Rotate +verb-categories-transfer = Set Transfer Amount diff --git a/Resources/Locale/en-US/weapons/ranged/barrels/components/server-battery-barrel-component.ftl b/Resources/Locale/en-US/weapons/ranged/barrels/components/server-battery-barrel-component.ftl deleted file mode 100644 index f4da8608d2..0000000000 --- a/Resources/Locale/en-US/weapons/ranged/barrels/components/server-battery-barrel-component.ftl +++ /dev/null @@ -1,4 +0,0 @@ -## EjectCellVerb - -eject-cell-verb-get-data-text = Eject cell -eject-cell-verb-get-data-text-no-cell = No cell \ No newline at end of file diff --git a/Resources/Locale/en-US/weapons/ranged/barrels/components/server-magazine-barrel-component.ftl b/Resources/Locale/en-US/weapons/ranged/barrels/components/server-magazine-barrel-component.ftl index 3cb9f91102..bdd84e5b93 100644 --- a/Resources/Locale/en-US/weapons/ranged/barrels/components/server-magazine-barrel-component.ftl +++ b/Resources/Locale/en-US/weapons/ranged/barrels/components/server-magazine-barrel-component.ftl @@ -13,7 +13,3 @@ server-magazine-barrel-component-interact-using-ammo-success = Ammo inserted server-magazine-barrel-component-interact-using-ammo-full = Chamber full server-magazine-barrel-component-on-examine = It uses [color=white]{$caliber}[/color] ammo. server-magazine-barrel-component-on-examine-magazine-type = It accepts [color=white]{$magazineType}[/color] magazines. - -## EjectMagazineVerb - -eject-magazine-verb-get-data-text = Eject magazine \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Markers/drag_shadow.yml b/Resources/Prototypes/Entities/Markers/drag_shadow.yml index 7e07384d3b..e0698af6a4 100644 --- a/Resources/Prototypes/Entities/Markers/drag_shadow.yml +++ b/Resources/Prototypes/Entities/Markers/drag_shadow.yml @@ -3,7 +3,9 @@ id: dragshadow abstract: true components: - - type: Sprite - layers: - - shader: unshaded - - type: HideContextMenu + - type: Tag + tags: + - HideContextMenu + - type: Sprite + layers: + - shader: unshaded diff --git a/Resources/Prototypes/Entities/Markers/pointing.yml b/Resources/Prototypes/Entities/Markers/pointing.yml index a4face9496..93f3738d99 100644 --- a/Resources/Prototypes/Entities/Markers/pointing.yml +++ b/Resources/Prototypes/Entities/Markers/pointing.yml @@ -2,6 +2,9 @@ name: pointing arrow id: pointingarrow components: + - type: Tag + tags: + - HideContextMenu - type: Sprite netsync: false sprite: Interface/Misc/pointing.rsi @@ -13,4 +16,3 @@ - type: Appearance visuals: - type: RoguePointingArrowVisualizer - - type: HideContextMenu diff --git a/Resources/Prototypes/Entities/Structures/Piping/Disposal/pipes.yml b/Resources/Prototypes/Entities/Structures/Piping/Disposal/pipes.yml index 00d4740938..0e45064c1d 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Disposal/pipes.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Disposal/pipes.yml @@ -137,7 +137,7 @@ state_anchored: pipe-j1s state_broken: pipe-b - type: Flippable - entity: DisposalRouterFlipped + mirrorEntity: DisposalRouterFlipped - type: UserInterface interfaces: - key: enum.DisposalRouterUiKey.Key @@ -170,7 +170,7 @@ state_anchored: pipe-j2s state_broken: pipe-b - type: Flippable - entity: DisposalRouter + mirrorEntity: DisposalRouter - type: Physics fixtures: - shape: @@ -200,7 +200,7 @@ state_anchored: pipe-j1 state_broken: pipe-b - type: Flippable - entity: DisposalJunctionFlipped + mirrorEntity: DisposalJunctionFlipped - type: Physics fixtures: - shape: @@ -229,7 +229,7 @@ state_anchored: pipe-j2 state_broken: pipe-b - type: Flippable - entity: DisposalJunction + mirrorEntity: DisposalJunction - type: Physics fixtures: - shape: diff --git a/Resources/Prototypes/Entities/Structures/Walls/low.yml b/Resources/Prototypes/Entities/Structures/Walls/low.yml index 88e753861f..42c948af39 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/low.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/low.yml @@ -40,8 +40,10 @@ name: low wall overlay abstract: true components: + - type: Tag + tags: + - HideContextMenu - type: Sprite color: "#889192" drawdepth: WallMountedItems sprite: Structures/Walls/low_wall.rsi - - type: HideContextMenu diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index a535e7775e..663782878c 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -210,3 +210,6 @@ - type: Tag id: EmitterBolt + +- type: Tag + id: HideContextMenu diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 640238fa82..161adea02c 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -162,6 +162,7 @@ binds: type: State key: MouseRight canFocus: true + priority: -1 # UIRightClick & EditorCancelPlace should fire first. - function: OpenCraftingMenu type: State key: G