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
This commit is contained in:
Leon Friedrich
2021-10-05 14:29:03 +11:00
committed by GitHub
parent 1095c8fc08
commit 6cb58e608b
175 changed files with 3391 additions and 4305 deletions

View File

@@ -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
{
/// <summary>
/// Client-side admin verb system. These usually open some sort of UIs.
/// </summary>
class AdminVerbSystem : EntitySystem
{
[Dependency] private readonly IClientConGroupController _clientConGroupController = default!;
[Dependency] private readonly IViewVariablesManager _viewVariablesManager = default!;
public override void Initialize()
{
SubscribeLocalEvent<GetOtherVerbsEvent>(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);
}
}
}
}

View File

@@ -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<IEntity> 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 }
};
AddChild(new PanelContainer
{
Children = { (List = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
}) },
PanelOverride = new StyleBoxFlat { BackgroundColor = MarginColor }
}}
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;
}
}
}

View File

@@ -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<ContextMenuPresenter>();
}
#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;
if (args.State != BoundKeyState.Down)
{
return false;
}
private void SystemOnToggleContextMenu(object? sender, PointerInputCmdHandler.PointerInputCmdArgs args)
{
if (_stateManager.CurrentState is not GameScreenBase)
{
return;
return false;
}
var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
if (playerEntity == null)
var player = _playerManager.LocalPlayer?.ControlledEntity;
if (player == null)
{
return;
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;
}
//visibility checks
player.TryGetContainer(out var playerContainer);
foreach (var entity in entities.ToList())
{
if (!entity.TryGetComponent(out ISpriteComponent? spriteComponent) ||
!spriteComponent.Visible ||
!CanSeeContainerCheck(entity, playerContainer))
{
entities.Remove(entity);
}
}
public void HandleMoveEvent(ref MoveEvent ev)
{
if (_contextMenuView.Elements.Count == 0) return;
var entity = ev.Sender;
if (_contextMenuView.Elements.ContainsKey(entity))
{
if (!entity.Transform.MapPosition.InRange(_mapCoordinates, 1.0f))
{
_contextMenuView.RemoveEntity(entity);
}
}
if (entities.Count == 0)
return false;
_contextMenuView.AddRootMenu(entities);
return true;
}
/// <summary>
/// Can the player see the entity through any entity containers?
/// </summary>
/// <remarks>
/// This is similar to <see cref="ContainerHelpers.IsInSameOrParentContainer()"/>, except that we do not
/// allow the player to be the "parent" container and we allow for see-through containers (display cases).
/// </remarks>
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;
}
/// <summary>
/// Check that entities in the context menu are still visible. If not, remove them from the context menu.
/// </summary>
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<IShowContextMenu>().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<ContextMenuPresenter>();
}
}
}

View File

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

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Client.GameObjects;
@@ -105,10 +105,14 @@ namespace Content.Client.ContextMenu.UI
public int GetHashCode(IEntity e)
{
var hash = EqualityComparer<string>.Default.GetHashCode(e.Prototype?.ID!);
foreach (var element in e.GetComponent<ISpriteComponent>().AllLayers.Where(obj => obj.Visible).Select(s => s.RsiState.Name))
if (e.TryGetComponent<ISpriteComponent>(out var sprite))
{
foreach (var element in sprite.AllLayers.Where(obj => obj.Visible).Select(s => s.RsiState.Name))
{
hash ^= EqualityComparer<string>.Default.GetHashCode(element!);
}
}
return hash;
}

View File

@@ -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<GetOtherVerbsEvent>(AddExamineVerb);
CommandBinds.Builder
.Bind(ContentKeyFunctions.ExamineEntity, new PointerInputCmdHandler(HandleExamine))
.Register<ExamineSystem>();
@@ -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

View File

@@ -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<ExamineSystem>().DoExamine(target);
}
}
}

View File

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

View File

@@ -78,7 +78,7 @@ namespace Content.Client.Items.Managers
else if (args.Function == ContentKeyFunctions.OpenContextMenu)
{
_entitySystemManager.GetEntitySystem<VerbSystem>()
.OpenContextMenu(item, _uiMgr.ScreenToUIPosition(args.PointerLocation));
.OpenVerbMenu(item, _uiMgr.ScreenToUIPosition(args.PointerLocation));
}
else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
{

View File

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

View File

@@ -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<PullableComponent, PullableMoveMessage>(OnPullableMove);
SubscribeLocalEvent<PullableComponent, PullableStopMovingMessage>(OnPullableStopMove);
SubscribeLocalEvent<SharedPullableComponent, PullableMoveMessage>(OnPullableMove);
SubscribeLocalEvent<SharedPullableComponent, PullableStopMovingMessage>(OnPullableStopMove);
}
}
}

View File

@@ -1,11 +0,0 @@
using Content.Shared.Rotatable;
using Robust.Shared.GameObjects;
namespace Content.Client.Rotatable
{
[RegisterComponent]
[ComponentReference(typeof(SharedRotatableComponent))]
public class RotatableComponent : SharedRotatableComponent
{
}
}

View File

@@ -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
{
/// <summary>
/// This pop-up appears when hovering over a verb category in the context menu.
/// </summary>
public sealed class VerbCategoryPopup : ContextMenuPopup
{
public VerbCategoryPopup(VerbSystem system, IEnumerable<Verb> 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;
/// <summary>
/// Whether or not to hide member verb text and just show icons.
/// </summary>
/// <remarks>
/// If no members have icons, this option is ignored and text is shown anyways. Defaults to using <see cref="VerbCategory.IconsOnly"/>.
/// </remarks>
private readonly bool _drawOnlyIcons;
/// <summary>
/// The pop-up that appears when hovering over this verb group.
/// </summary>
private readonly VerbCategoryPopup _popup;
public VerbCategoryButton(VerbSystem system, VerbCategory category, IEnumerable<Verb> 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<IResourceCache>()
.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);
}
}
/// <summary>
/// Open a verb category pop-up after a short delay.
/// </summary>
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);
}
/// <summary>
/// Cancel the delayed pop-up
/// </summary>
protected override void MouseExited()
{
base.MouseExited();
_openCancel?.Cancel();
_openCancel = null;
}
}
}

View File

@@ -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<PointerInputCmdHandler.PointerInputCmdArgs>? ToggleContextMenu;
public event EventHandler<bool>? 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<VerbType, SortedSet<Verb>> CurrentVerbs = new();
// TODO: Move presenter out of the system
// TODO: Separate the rest of the UI from the logic
/// <summary>
/// Whether to show all entities on the context menu.
/// </summary>
/// <remarks>
/// Verb execution will only be affected if the server also agrees that this player can see the target
/// entity.
/// </remarks>
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<RoundRestartCleanupEvent>(Reset);
SubscribeNetworkEvent<VerbSystemMessages.VerbsResponseMessage>(FillEntityPopup);
SubscribeNetworkEvent<PlayerContainerVisibilityMessage>(HandleContainerVisibilityMessage);
SubscribeNetworkEvent<VerbsResponseEvent>(HandleVerbResponse);
SubscribeNetworkEvent<SetSeeAllContextEvent>(SetSeeAllContext);
_contextMenuPresenter = new ContextMenuPresenter(this);
SubscribeLocalEvent<MoveEvent>(_contextMenuPresenter.HandleMoveEvent);
ContextMenuPresenter = new ContextMenuPresenter(this);
}
CommandBinds.Builder
.Bind(ContentKeyFunctions.OpenContextMenu,
new PointerInputCmdHandler(HandleOpenContextMenu))
.Register<VerbSystem>();
private void Reset(RoundRestartCleanupEvent ev)
{
ContextMenuPresenter.CloseAllMenus();
}
public override void Shutdown()
{
base.Shutdown();
_contextMenuPresenter?.Dispose();
CommandBinds.Unregister<VerbSystem>();
}
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;
}
/// <summary>
/// Execute actions associated with the given verb. If there are no defined actions, this will instead ask
/// the server to run the given verb.
/// </summary>
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<string, List<ListedVerbData>>();
var groupIcons = new Dictionary<string, SpriteSpecifier>();
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);
// update "waiting for server...".
CurrentVerbPopup.List.DisposeAllChildren();
CurrentVerbPopup.AddToMenu(new Label { Text = Loc.GetString("verb-system-null-server-response") });
FillVerbPopup(CurrentVerbPopup);
return;
}
list.Add(new ListedVerbData(data.Text, !data.Available, data.Key, entity.ToString()!, () =>
// Add the new server-side verbs.
foreach (var (verbType, verbSet) in msg.Verbs)
{
RaiseNetworkEvent(new VerbSystemMessages.UseVerbMessage(curEntity, data.Key));
CloseAllMenus();
}, data.Icon));
}
var user = GetUserEntity();
//Get verbs, component dependent.
foreach (var (component, verb) in VerbUtility.GetVerbs(entity))
SortedSet<Verb> sortedVerbs = new (verbSet);
if (!CurrentVerbs.TryAdd(verbType, sortedVerbs))
{
if (!VerbUtility.VerbAccessChecks(user, entity, verb))
{
continue;
}
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()))
{
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));
}
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));
CurrentVerbs[verbType].UnionWith(sortedVerbs);
}
}
// Clear currently shown verbs and show new ones
CurrentVerbPopup.List.DisposeAllChildren();
FillVerbPopup(CurrentVerbPopup);
}
else
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)
{
AddVerbSet(popup, type);
}
// 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);
}
private VerbButton CreateVerbButton(ListedVerbData data)
{
var button = new VerbButton
{
Text = Loc.GetString(data.Text),
Disabled = data.Disabled
};
if (data.Icon != null)
{
button.Icon = data.Icon.Frame0();
popup.InvalidateMeasure();
}
if (!data.Disabled)
/// <summary>
/// Add a list of verbs to a BoxContainer. Iterates over the given verbs list and creates GUI buttons.
/// </summary>
private void AddVerbSet(ContextMenuPopup popup, VerbType type)
{
button.OnPressed += _ =>
if (!CurrentVerbs.TryGetValue(type, out var verbSet) || verbSet.Count == 0)
return;
HashSet<string> listedCategories = new();
foreach (var verb in verbSet)
{
CloseAllMenus();
try
if (verb.Category == null)
{
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;
}
return button;
if (listedCategories.Contains(verb.Category.Text))
{
// This verb was already included in a verb-category button added by a previous verb
continue;
}
private Control CreateCategoryButton(string text, List<ListedVerbData> verbButtons, SpriteSpecifier? icon)
{
verbButtons.Sort((a, b) => string.Compare(a.Text, b.Text, StringComparison.CurrentCulture));
// Get the verbs in the category
var verbsInCategory = verbSet.Where(v => v.Category?.Text == verb.Category.Text);
return new VerbGroupButton(this, verbButtons, icon)
{
Text = Loc.GetString(text),
};
popup.AddToMenu(
new VerbCategoryButton(this, verb.Category, verbsInCategory, type, CurrentTarget));
listedCategories.Add(verb.Category.Text);
continue;
}
}
public void CloseVerbMenu()
{
_currentVerbListRoot?.Dispose();
_currentVerbListRoot = null;
_currentEntity = EntityUid.Invalid;
if (CurrentVerbPopup != null)
{
CurrentVerbPopup.OnPopupHide -= CloseVerbMenu;
CurrentVerbPopup.Dispose();
CurrentVerbPopup = null;
}
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()
{
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;
}
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<ListedVerbData> 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<ListedVerbData> 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<IResourceCache>()
.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();
}
}
}

View File

@@ -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
{
/// <summary>
/// Global verb that opens a view variables window for the entity in question.
/// </summary>
[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<IClientConGroupController>();
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<IViewVariablesManager>();
vvm.OpenVV(target);
}
}
}

View File

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

View File

@@ -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<ContainerSlot>(Owner, $"{Name}-privilegedId");
_targetIdContainer = ContainerHelpers.EnsureContainer<ContainerSlot>(Owner, $"{Name}-targetId");
PrivilegedIdContainer = ContainerHelpers.EnsureContainer<ContainerSlot>(Owner, $"{Name}-privilegedId");
TargetIdContainer = ContainerHelpers.EnsureContainer<ContainerSlot>(Owner, $"{Name}-targetId");
Owner.EnsureComponentWarn<AccessReader>();
Owner.EnsureComponentWarn<ServerUserInterfaceComponent>();
@@ -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
}
/// <summary>
/// Returns true if there is an ID in <see cref="_privilegedIdContainer"/> and said ID satisfies the requirements of <see cref="AccessReader"/>.
/// Returns true if there is an ID in <see cref="PrivilegedIdContainer"/> and said ID satisfies the requirements of <see cref="AccessReader"/>.
/// </summary>
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);
}
/// <summary>
/// Called when the "Submit" button in the UI gets pressed.
/// Writes data passed from the UI into the ID stored in <see cref="_targetIdContainer"/>, if present.
/// Writes data passed from the UI into the ID stored in <see cref="TargetIdContainer"/>, if present.
/// </summary>
private void TryWriteToTargetId(string newFullName, string newJobTitle, List<string> newAccessList)
{
if (!PrivilegedIdIsAuthorized() || _targetIdContainer.ContainedEntity == null)
if (!PrivilegedIdIsAuthorized() || TargetIdContainer.ContainedEntity == null)
{
return;
}
var targetIdEntity = _targetIdContainer.ContainedEntity;
var targetIdEntity = TargetIdContainer.ContainedEntity;
var targetIdComponent = targetIdEntity.GetComponent<IdCardComponent>();
targetIdComponent.FullName = newFullName;
@@ -128,7 +125,7 @@ namespace Content.Server.Access.Components
/// </summary>
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)
{
var isId = hands.GetActiveHand?.Owner.HasComponent<IdCardComponent>();
if (isId != true)
public void InsertIdFromHand(IEntity user, ContainerSlot container, SharedHandsComponent hands)
{
if (!hands.TryGetActiveHeldEntity(out var heldEntity))
return;
}
if (hands.ActiveHand == null)
{
if (!heldEntity.HasComponent<IdCardComponent>())
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<ItemComponent>());
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<IdCardComponent>() || !user.TryGetComponent(out IHandsComponent? hand))
if (!item.HasComponent<IdCardComponent>() || !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<IdCardConsoleComponent>
{
public override bool AlternativeInteraction => true;
protected override void GetData(IEntity user, IdCardConsoleComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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<IdCardConsoleComponent>
{
public override bool AlternativeInteraction => true;
protected override void GetData(IEntity user, IdCardConsoleComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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);
}
}
}

View File

@@ -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<IdCardConsoleComponent, GetInteractionVerbsEvent>(AddInsertVerbs);
SubscribeLocalEvent<IdCardConsoleComponent, GetAlternativeVerbsEvent>(AddEjectVerbs);
}
private void AddInsertVerbs(EntityUid uid, IdCardConsoleComponent component, GetInteractionVerbsEvent args)
{
if (args.Using == null ||
!args.CanAccess ||
!args.CanInteract ||
!args.Using.HasComponent<IdCardComponent>() ||
!_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);
}
}
}
}

View File

@@ -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
{
/// <summary>
/// System to provide various global admin/debug verbs
/// </summary>
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<GetOtherVerbsEvent>(AddDebugVerbs);
}
private void AddDebugVerbs(GetOtherVerbsEvent args)
{
if (!args.User.TryGetComponent<ActorComponent>(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<MindComponent>() &&
args.Target.TryGetComponent<MindComponent>(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<MindComponent>())
{
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<InventoryComponent>())
{
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<IDisposalTubeComponent>(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<MindComponent>()?.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<ConfigurationComponent>(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<SolutionContainerManagerComponent>())
{
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);
}
}
}
}

View File

@@ -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<IEntityManager>();
@@ -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<IMobStateComponent>()?.UpdateState(0);
target.GetComponentOrNull<HungerComponent>()?.ResetFood();
target.GetComponentOrNull<ThirstComponent>()?.ResetThirst();
target.GetComponentOrNull<StunnableComponent>()?.ResetStuns();
EntitySystem.Get<FlammableSystem>().Extinguish(target.Uid);
if (target.TryGetComponent(out DamageableComponent? damageable))
{
EntitySystem.Get<DamageableSystem>().SetAllDamage(damageable, 0);
}
if (target.TryGetComponent(out CreamPiedComponent? creamPied))
{
EntitySystem.Get<CreamPieSystem>().SetCreamPied(target.Uid, creamPied, false);
}
}
}

View File

@@ -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<SolutionContainerSystem>()
.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<SolutionContainerSystem>();
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;
}
}
}
}

View File

@@ -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<EuiManager>();
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<SolutionContainerManagerComponent>()
&& target.HasComponent<InjectableSolutionComponent>()))
{
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<IAdminManager>();
if (user.TryGetComponent<ActorComponent>(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<IAdminManager>();
if (user.TryGetComponent<ActorComponent>(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<SolutionContainerSystem>()
.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<SolutionContainerSystem>();
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;
}
}
}
}
}

View File

@@ -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<IConGroupController>();
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<IConGroupController>();
if (!user.TryGetComponent(out ActorComponent? actor))
{
return;
}
if (!groupController.CanCommand(actor.PlayerSession, "deleteentity"))
{
return;
}
target.Delete();
}
}
}

View File

@@ -16,10 +16,9 @@ namespace Content.Server.Alert.Click
{
public void AlertClicked(ClickAlertEventArgs args)
{
var ps = EntitySystem.Get<SharedPullingSystem>();
if (args.Player.TryGetComponent<SharedPullableComponent>(out var playerPullable))
{
ps.TryStopPull(playerPullable);
EntitySystem.Get<SharedPullingSystem>().TryStopPull(playerPullable);
}
}
}

View File

@@ -1,4 +1,4 @@
using Content.Shared.Alert;
using Content.Shared.Alert;
using Content.Shared.Pulling;
using Content.Shared.Pulling.Components;
using JetBrains.Annotations;

View File

@@ -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);
}
/// <summary>
/// Open interaction window
/// </summary>
[Verb]
private sealed class ControlVerb : Verb<GasTankComponent>
{
public override bool RequireInteractionRange => true;
protected override void GetData(IEntity user, GasTankComponent component, VerbData data)
{
data.Visibility = VerbVisibility.Invisible;
if (!user.HasComponent<ActorComponent>())
{
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<ActorComponent>(out var actor))
{
return;
}
component.OpenInterface(actor.PlayerSession);
}
}
}
[UsedImplicitly]

View File

@@ -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<GasTankComponent, GetActivationVerbsEvent>(AddOpenUIVerb);
}
private void AddOpenUIVerb(EntityUid uid, GasTankComponent component, GetActivationVerbsEvent args)
{
if (!args.CanAccess || !args.User.TryGetComponent<ActorComponent>(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);

View File

@@ -99,7 +99,7 @@ namespace Content.Server.Body.Commands
return;
}
body.SetPart($"{nameof(AttachBodyPartVerb)}-{partEntity.Uid}", part);
body.SetPart($"AttachBodyPartVerb-{partEntity.Uid}", part);
}
}
}

View File

@@ -218,54 +218,5 @@ namespace Content.Server.Body.Part
break;
}
}
[Verb]
public class AttachBodyPartVerb : Verb<BodyPartComponent>
{
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<IConGroupController>();
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);
}
}
}
}

View File

@@ -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;
}
/// <summary>
/// Allows the unbuckling of the owning entity through a verb if
/// anyone right clicks them.
/// </summary>
[Verb]
private sealed class BuckleVerb : Verb<BuckleComponent>
{
protected override void GetData(IEntity user, BuckleComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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);
}
}
}
}

View File

@@ -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<StrapComponent>
{
protected override void GetData(IEntity user, StrapComponent component, VerbData data)
{
data.Visibility = VerbVisibility.Invisible;
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(component.Owner) ||
!user.TryGetComponent<BuckleComponent>(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<BuckleComponent>(out var buckle))
{
return;
}
buckle.ToggleBuckle(user, component.Owner);
}
}
public override bool DragDropOn(DragDropEvent eventArgs)
{
if (!eventArgs.Dragged.TryGetComponent(out BuckleComponent? buckleComponent)) return false;

View File

@@ -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<StrapComponent, EntRemovedFromContainerMessage>(ContainerModifiedStrap);
SubscribeLocalEvent<BuckleComponent, InteractHandEvent>(HandleInteractHand);
SubscribeLocalEvent<BuckleComponent, GetInteractionVerbsEvent>(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)

View File

@@ -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<StrapComponent, GetInteractionVerbsEvent>(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<BuckleComponent>();
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<BuckleComponent>(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<BuckleComponent>(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<ActorComponent>() ? 1 : -1;
args.Verbs.Add(verb);
}
}
}
}

View File

@@ -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<ItemCabinetComponent>
{
protected override void GetData(IEntity user, ItemCabinetComponent component, VerbData data)
{
if (component.ItemContainer.ContainedEntity == null || !component.Opened || !EntitySystem.Get<ActionBlockerSystem>().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<ItemCabinetComponent>
{
// 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<ActionBlockerSystem>().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);
}
}
}
}

View File

@@ -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<ItemCabinetComponent, TryEjectItemCabinetEvent>(OnTryEjectItemCabinet);
SubscribeLocalEvent<ItemCabinetComponent, TryInsertItemCabinetEvent>(OnTryInsertItemCabinet);
SubscribeLocalEvent<ItemCabinetComponent, ToggleItemCabinetEvent>(OnToggleItemCabinet);
SubscribeLocalEvent<ItemCabinetComponent, GetInteractionVerbsEvent>(AddEjectInsertVerbs);
SubscribeLocalEvent<ItemCabinetComponent, GetActivationVerbsEvent>(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
/// <summary>
/// Toggles the ItemCabinet's state.
/// </summary>
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<ItemComponent>(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);
}
/// <summary>
/// Tries to eject the ItemCabinet's item, either into the user's hands. Used by both <see
/// cref="OnTryEjectItemCabinet"/> and the eject verbs.
/// </summary>
private static void TakeItem(ItemCabinetComponent comp, SharedHandsComponent hands, IEntity containedEntity, IEntity user)
{
if (containedEntity.HasComponent<ItemComponent>())
{
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))

View File

@@ -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<ContainerSlot>(Owner, $"{Name}-reagentContainerContainer");
_bufferSolution = EntitySystem.Get<SolutionContainerSystem>().EnsureSolution(Owner, SolutionName);
@@ -177,7 +176,7 @@ namespace Content.Server.Chemistry.Components
/// <returns>Returns a <see cref="SharedChemMasterComponent.ChemMasterBoundUserInterfaceState"/></returns>
private ChemMasterBoundUserInterfaceState GetUserInterfaceState()
{
var beaker = _beakerContainer.ContainedEntity;
var beaker = BeakerContainer.ContainedEntity;
EntitySystem.Get<SolutionContainerSystem>().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 <see cref="Solution"/>, eject it.
/// Tries to eject into user's hands first, then ejects onto chem master if both hands are full.
/// </summary>
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<HandsComponent>(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<ChemMasterComponent>
{
public override bool AlternativeInteraction => true;
protected override void GetData(IEntity user, ChemMasterComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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);
}
}
}
}

View File

@@ -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<ContainerSlot>(Owner, $"{Name}-reagentContainerContainer");
InitializeFromPrototype();
@@ -228,7 +227,7 @@ namespace Content.Server.Chemistry.Components
/// <returns>Returns a <see cref="SharedReagentDispenserComponent.ReagentDispenserBoundUserInterfaceState"/></returns>
private ReagentDispenserBoundUserInterfaceState GetUserInterfaceState()
{
var beaker = _beakerContainer.ContainedEntity;
var beaker = BeakerContainer.ContainedEntity;
if (beaker == null || !beaker.TryGetComponent(out FitsInDispenserComponent? fits) ||
!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(beaker, fits.Solution, out var solution))
{
@@ -252,16 +251,16 @@ namespace Content.Server.Chemistry.Components
/// If this component contains an entity with a <see cref="SolutionHolder"/>, eject it.
/// Tries to eject into user's hands first, then ejects onto dispenser if both hands are full.
/// </summary>
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<HandsComponent>(out var hands) ||
@@ -276,12 +275,12 @@ namespace Content.Server.Chemistry.Components
/// </summary>
private void TryClear()
{
if (!HasBeaker || !_beakerContainer.ContainedEntity!.TryGetComponent(out FitsInDispenserComponent? fits) ||
if (!HasBeaker || !BeakerContainer.ContainedEntity!.TryGetComponent(out FitsInDispenserComponent? fits) ||
!EntitySystem.Get<SolutionContainerSystem>()
.TryGetSolution(_beakerContainer.ContainedEntity, fits.Solution, out var solution))
.TryGetSolution(BeakerContainer.ContainedEntity, fits.Solution, out var solution))
return;
EntitySystem.Get<SolutionContainerSystem>().RemoveAllSolution(_beakerContainer.ContainedEntity!.Uid, solution);
EntitySystem.Get<SolutionContainerSystem>().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<SolutionContainerSystem>()
.TryGetSolution(_beakerContainer.ContainedEntity, fits.Solution, out var solution)) return;
.TryGetSolution(BeakerContainer.ContainedEntity, fits.Solution, out var solution)) return;
EntitySystem.Get<SolutionContainerSystem>()
.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<ReagentDispenserComponent>
{
public override bool AlternativeInteraction => true;
protected override void GetData(IEntity user, ReagentDispenserComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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<ReagentDispenserInventoryEntry>
{
public override int Compare(ReagentDispenserInventoryEntry x, ReagentDispenserInventoryEntry y)

View File

@@ -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);
/// <summary>
/// Subjectively, which transfer amount would be best for most activities given the maximum
/// transfer amount.
/// </summary>
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)
};
/// <summary>
/// Can this entity take reagent from reagent tanks?
/// </summary>
@@ -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<SolutionTransferComponent>
{
protected override void GetData(IEntity user, SolutionTransferComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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<SolutionTransferComponent>
{
protected override void GetData(IEntity user, SolutionTransferComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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<SolutionTransferComponent>
{
protected override void GetData(IEntity user, SolutionTransferComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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<SolutionTransferComponent>
{
public override bool AlternativeInteraction => true;
protected override void GetData(IEntity user, SolutionTransferComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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<ActorComponent>(out var actor))
{
return;
}
component.UserInterface?.Open(actor.PlayerSession);
}
}
}
}

View File

@@ -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<ChemMasterComponent, SolutionChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<ChemMasterComponent, GetInteractionVerbsEvent>(AddInsertVerb);
SubscribeLocalEvent<ChemMasterComponent, GetAlternativeVerbsEvent>(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<FitsInDispenserComponent>() ||
!_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,

View File

@@ -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<ReagentDispenserComponent, SolutionChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<ReagentDispenserComponent, GetAlternativeVerbsEvent>(AddEjectVerb);
SubscribeLocalEvent<ReagentDispenserComponent, GetInteractionVerbsEvent>(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<FitsInDispenserComponent>() ||
!_actionBlockerSystem.CanDrop(args.User))
return;
if (!args.Using.HasComponent<FitsInDispenserComponent>() ||
!_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();

View File

@@ -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
{
/// <summary>
/// Default transfer amounts for the set-transfer verb.
/// </summary>
public static readonly List<int> DefaultTransferAmounts = new() { 1, 5, 10, 25, 50, 100, 250, 500, 1000};
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SolutionTransferComponent, GetAlternativeVerbsEvent>(AddSetTransferVerbs);
}
private void AddSetTransferVerbs(EntityUid uid, SolutionTransferComponent component, GetAlternativeVerbsEvent args)
{
if (!args.CanAccess || !args.CanInteract || !component.CanChangeTransferAmount)
return;
if (!args.User.TryGetComponent<ActorComponent>(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);
}
}
}
}

View File

@@ -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<ClimbingComponent> _activeClimbers = new();
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<ClimbableComponent, GetAlternativeVerbsEvent>(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)

View File

@@ -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);
}
}
/// <summary>
/// Allows you to vault an object with the ClimbableComponent through right click
/// </summary>
[Verb]
private sealed class ClimbVerb : Verb<ClimbableComponent>
{
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);
}
}
}
}

View File

@@ -118,26 +118,6 @@ namespace Content.Server.Clothing.Components
{
return new MagbootsComponentState(On);
}
[UsedImplicitly]
public sealed class ToggleMagbootsVerb : Verb<MagbootsComponent>
{
protected override void GetData(IEntity user, MagbootsComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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]

View File

@@ -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<MagbootsComponent, GetActivationVerbsEvent>(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);
}
}
}

View File

@@ -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<ConfigurationComponent>
{
protected override void GetData(IEntity user, ConfigurationComponent component, VerbData data)
{
var session = user.PlayerSession();
var groupController = IoCManager.Resolve<IConGroupController>();
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);
}
}
}
}
}

View File

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

View File

@@ -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<ConstructionComponent>
{
protected override void GetData(IEntity user, ConstructionComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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"));
}
}
}
}
}

View File

@@ -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<TryStartStructureConstructionMessage>(HandleStartStructureConstruction);
SubscribeNetworkEvent<TryStartItemConstructionMessage>(HandleStartItemConstruction);
SubscribeLocalEvent<ConstructionComponent, GetOtherVerbsEvent>(AddDeconstructVerb);
SubscribeLocalEvent<ConstructionComponent, ExaminedEvent>(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<IEntity> EnumerateNearby(IEntity user)

View File

@@ -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<VerbSystem>().RemoveContainerVisibility(player);
}
}
}

View File

@@ -313,31 +313,5 @@ namespace Content.Server.Cuffs.Components
return;
}
/// <summary>
/// Allows the uncuffing of a cuffed person. Used by other people and by the component owner to break out of cuffs.
/// </summary>
[Verb]
private sealed class UncuffVerb : Verb<CuffableComponent>
{
protected override void GetData(IEntity user, CuffableComponent component, VerbData data)
{
if ((user != component.Owner && !EntitySystem.Get<ActionBlockerSystem>().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);
}
}
}
}
}

View File

@@ -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<HandCountChangedEvent>(OnHandCountChanged);
SubscribeLocalEvent<UncuffAttemptEvent>(OnUncuffAttempt);
SubscribeLocalEvent<CuffableComponent, GetOtherVerbsEvent>(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<IMobStateComponent>(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

View File

@@ -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
{
/// <summary>
/// Completely removes all damage from the DamageableComponent (heals the mob).
/// </summary>
[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<IConGroupController>();
if (user.TryGetComponent<ActorComponent>(out var player))
{
if (!target.HasComponent<DamageableComponent>() && !target.HasComponent<HungerComponent>() &&
!target.HasComponent<ThirstComponent>())
{
return;
}
if (groupController.CanCommand(player.PlayerSession, "rejuvenate"))
{
data.Visibility = VerbVisibility.Visible;
}
}
}
public override void Activate(IEntity user, IEntity target)
{
var groupController = IoCManager.Resolve<IConGroupController>();
if (user.TryGetComponent<ActorComponent>(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<DamageableSystem>().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<FlammableSystem>().Extinguish(target.Uid, flammable);
}
if (target.TryGetComponent(out CreamPiedComponent? creamPied))
{
EntitySystem.Get<CreamPieSystem>().SetCreamPied(target.Uid, creamPied, false);
}
}
}
}

View File

@@ -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<DisposalRouterComponent>
{
protected override void GetData(IEntity user, DisposalRouterComponent component, VerbData data)
{
var session = user.PlayerSession();
var groupController = IoCManager.Resolve<IConGroupController>();
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);
}
}
}
}
}

View File

@@ -150,34 +150,7 @@ namespace Content.Server.Disposal.Tube.Components
base.OnRemove();
UserInterface?.CloseAll();
}
[Verb]
public sealed class ConfigureVerb : Verb<DisposalTaggerComponent>
{
protected override void GetData(IEntity user, DisposalTaggerComponent component, VerbData data)
{
var groupController = IoCManager.Resolve<IConGroupController>();
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);

View File

@@ -252,39 +252,5 @@ namespace Content.Server.Disposal.Tube.Components
Disconnect();
UpdateVisualState();
}
[Verb]
private sealed class TubeDirectionsVerb : Verb<IDisposalTubeComponent>
{
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<IConGroupController>();
if (user.TryGetComponent<ActorComponent>(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<IConGroupController>();
if (user.TryGetComponent<ActorComponent>(out var player))
{
if (groupController.CanCommand(player.PlayerSession, "tubeconnections"))
{
component.PopupDirections(user);
}
}
}
}
}
}

View File

@@ -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<DisposalTubeComponent, PhysicsBodyTypeChangedEvent>(BodyTypeChanged);
SubscribeLocalEvent<DisposalTubeComponent, RelayMovementEntityEvent>(OnRelayMovement);
SubscribeLocalEvent<DisposalTaggerComponent, GetInteractionVerbsEvent>(AddOpenUIVerbs);
SubscribeLocalEvent<DisposalRouterComponent, GetInteractionVerbsEvent>(AddOpenUIVerbs);
}
private void AddOpenUIVerbs(EntityUid uid, DisposalTaggerComponent component, GetInteractionVerbsEvent args)
{
if (!args.CanAccess || !args.CanInteract)
return;
if (!args.User.TryGetComponent<ActorComponent>(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<ActorComponent>(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)

View File

@@ -193,84 +193,6 @@ namespace Content.Server.Disposal.Unit.Components
return true;
}
[Verb]
private sealed class SelfInsertVerb : Verb<DisposalUnitComponent>
{
protected override void GetData(IEntity user, DisposalUnitComponent component, VerbData data)
{
data.Visibility = VerbVisibility.Invisible;
if (!EntitySystem.Get<ActionBlockerSystem>().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<DisposalUnitComponent>
{
protected override void GetData(IEntity user, DisposalUnitComponent component, VerbData data)
{
data.Visibility = VerbVisibility.Invisible;
if (!EntitySystem.Get<ActionBlockerSystem>().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<DisposalUnitSystem>().Engage(component);
}
}
[Verb]
private sealed class EjectVerb : Verb<DisposalUnitComponent>
{
public override bool AlternativeInteraction => true;
protected override void GetData(IEntity user, DisposalUnitComponent component, VerbData data)
{
data.Visibility = VerbVisibility.Invisible;
if (!EntitySystem.Get<ActionBlockerSystem>().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<DisposalUnitSystem>().TryEjectContents(component);
}
}
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
{
EntitySystem.Get<DisposalUnitSystem>().TryEjectContents(this);

View File

@@ -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<DisposalUnitComponent> _activeDisposals = new();
@@ -58,6 +59,51 @@ namespace Content.Server.Disposal.Unit.EntitySystems
SubscribeLocalEvent<DisposalUnitComponent, ActivateInWorldEvent>(HandleActivate);
SubscribeLocalEvent<DisposalUnitComponent, InteractHandEvent>(HandleInteractHand);
SubscribeLocalEvent<DisposalUnitComponent, InteractUsingEvent>(HandleInteractUsing);
// Verbs
SubscribeLocalEvent<DisposalUnitComponent, GetAlternativeVerbsEvent>(AddFlushEjectVerbs);
SubscribeLocalEvent<DisposalUnitComponent, GetOtherVerbsEvent>(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)

View File

@@ -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";
/// <summary>
/// Transfers solution from the held container to the floor.
/// </summary>
[Verb]
private sealed class SpillTargetVerb : Verb<SpillableComponent>
{
protected override void GetData(IEntity user, SpillableComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user) ||
!EntitySystem.Get<SolutionContainerSystem>()
.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<SolutionContainerSystem>();
if (component.Owner.HasComponent<SolutionContainerManagerComponent>())
{
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<SolutionContainerSystem>()
.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

View File

@@ -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<SpillableComponent, GetOtherVerbsEvent>(AddSpillVerb);
SubscribeLocalEvent<PuddleComponent, ExaminedEvent>(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<SlipperyComponent>(uid, out var slippery) && slippery.Slippery)

View File

@@ -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<IConGroupController>();
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<IConGroupController>();
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>();
ghostRoleSystem.OpenMakeGhostRoleEui(actor.PlayerSession, target.Uid);
}
}
}

View File

@@ -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<PullingSystem>().TryStopPull(pullable);

View File

@@ -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<IConGroupController>();
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<IConGroupController>();
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);
}
}
}

View File

@@ -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.
/// </summary>
/// <remarks>
/// 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 <see
/// cref="UserInteraction(IEntity, EntityCoordinates, EntityUid, bool)"/>
/// Uses the context menu verb list, and acts out the highest priority alternative interaction verb.
/// </remarks>
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;
}
}

View File

@@ -614,47 +614,5 @@ namespace Content.Server.Inventory.Components
return false;
}
[Verb]
private sealed class SetOutfitVerb : Verb<InventoryComponent>
{
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<IServerConsoleHost>();
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<IConGroupController>();
return user.TryGetComponent<ActorComponent>(out var player) &&
groupController.CanCommand(player.PlayerSession, "setoutfit");
}
}
}
}

View File

@@ -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<ItemComponent>
{
protected override void GetData(IEntity user, ItemComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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);
}
}
}
}
}

View File

@@ -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<SharedItemComponent, GetInteractionVerbsEvent>(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);
}
}
}

View File

@@ -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
/// <summary>
/// Enables the light if it is not active. Once active it cannot be turned off.
/// </summary>
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<ExpendableLightComponent>
{
protected override void GetData(IEntity user, ExpendableLightComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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();
}
}
}
}

View File

@@ -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<HandheldLightComponent>
{
protected override void GetData(IEntity user, HandheldLightComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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]

View File

@@ -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<UnpoweredFlashlightComponent>
{
protected override void GetData(IEntity user, UnpoweredFlashlightComponent component, VerbData data)
{
var canInteract = EntitySystem.Get<ActionBlockerSystem>().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<UnpoweredFlashlightSystem>().ToggleLight(component);
}
}
}
}

View File

@@ -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<ExpendableLightComponent, GetActivationVerbsEvent>(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);
}
}
}

View File

@@ -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<ActivateHandheldLightMessage>(HandleActivate);
SubscribeLocalEvent<DeactivateHandheldLightMessage>(HandleDeactivate);
SubscribeLocalEvent<HandheldLightComponent, GetActivationVerbsEvent>(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);
}
}
}

View File

@@ -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<UnpoweredFlashlightComponent, GetActivationVerbsEvent>(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))

View File

@@ -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<LockComponent>
{
public override bool AlternativeInteraction => true;
protected override void GetData(IEntity user, LockComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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<ActionBlockerSystem>().CanInteract(user) ||
!user.InRangeUnobstructed(component))
{
return;
}
// Call relevant entity system
var lockSystem = user.EntityManager.EntitySysManager.GetEntitySystem<LockSystem>();
if (component.Locked)
{
lockSystem.DoUnlock(component.Owner.Uid, user, component);
}
else
{
lockSystem.DoLock(component.Owner.Uid, user, component);
}
}
}
}
}

View File

@@ -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<LockComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<LockComponent, ActivateInWorldEvent>(OnActivated);
SubscribeLocalEvent<LockComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<LockComponent, GetAlternativeVerbsEvent>(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))
{
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);
}
}
}

View File

@@ -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<MedicalScannerComponent>
{
protected override void GetData(IEntity user, MedicalScannerComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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<MedicalScannerComponent>
{
public override bool AlternativeInteraction => true;
protected override void GetData(IEntity user, MedicalScannerComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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);

View File

@@ -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<MedicalScannerComponent, RelayMovementEntityEvent>(OnRelayMovement);
SubscribeLocalEvent<MedicalScannerComponent, GetInteractionVerbsEvent>(AddInsertOtherVerb);
SubscribeLocalEvent<MedicalScannerComponent, GetAlternativeVerbsEvent>(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)

View File

@@ -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<SharedPlayerMobMoverComponent>();
entity.EnsureComponent<SharedSpeechComponent>();
entity.EnsureComponent<SharedEmotingComponent>();
entity.EnsureComponent<ExaminerComponent>();
});
}
}

View File

@@ -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<IConGroupController>();
if (user == target)
{
return;
}
if (user.TryGetComponent<ActorComponent>(out var player))
{
if (!user.HasComponent<MindComponent>() || !target.HasComponent<MindComponent>())
{
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<IConGroupController>();
var player = user.GetComponent<ActorComponent>().PlayerSession;
if (!groupController.CanCommand(player, "controlmob"))
{
return;
}
var userMind = player.ContentData()?.Mind;
var targetMind = target.GetComponent<MindComponent>();
targetMind.Mind?.TransferTo(null);
userMind?.TransferTo(target, ghostCheckOverride: true);
}
}
}

View File

@@ -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<IConGroupController>();
if (user == target || target.HasComponent<MindComponent>())
return;
var player = user.GetComponent<ActorComponent>().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<IConGroupController>();
var player = user.GetComponent<ActorComponent>().PlayerSession;
if (!groupController.CanCommand(player, "makesentient"))
return;
var host = IoCManager.Resolve<IServerConsoleHost>();
var cmd = new MakeSentientCommand();
var uidStr = target.Uid.ToString();
cmd.Execute(new ConsoleShell(host, player), $"{cmd.Command} {uidStr}",
new[] {uidStr});
}
}
}

View File

@@ -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<BodyBagEntityStorageComponent>
{
protected override void GetData(IEntity user, BodyBagEntityStorageComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user) || component.LabelContainer?.ContainedEntity == null)
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Text = Loc.GetString("remove-label-verb-get-data-text");
}
/// <inheritdoc />
protected override void Activate(IEntity user, BodyBagEntityStorageComponent component)
{
component.RemoveLabel(user);
}
}
}
}

View File

@@ -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<CrematoriumEntityStorageComponent>
{
protected override void GetData(IEntity user, CrematoriumEntityStorageComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user) || component.Cooking || component.Open)
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Text = Loc.GetString("cremate-verb-get-data-text");
}
/// <inheritdoc />
protected override void Activate(IEntity user, CrematoriumEntityStorageComponent component)
{
component.TryCremate();
}
}
}
}

View File

@@ -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<CrematoriumEntityStorageComponent, GetAlternativeVerbsEvent>(AddCremateVerb);
SubscribeLocalEvent<BodyBagEntityStorageComponent, GetAlternativeVerbsEvent>(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);
}
/// <summary>
/// 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.
/// </summary>
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;

View File

@@ -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<PDAComponent>
{
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<ActionBlockerSystem>().CanInteract(user))
return;
var item = EntitySystem.Get<SharedItemSlotsSystem>().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<SharedItemSlotsSystem>().
TryEjectContent(itemSlots, PenSlotName, user);
}
}
}
[Verb]
public sealed class EjectIDVerb : Verb<PDAComponent>
{
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<ActionBlockerSystem>().CanInteract(user))
return;
var item = EntitySystem.Get<SharedItemSlotsSystem>().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<SharedItemSlotsSystem>().
TryEjectContent(itemSlots, IDSlotName, user);
}
}
}
#endregion
}
}

View File

@@ -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<PDAComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<PDAComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<PDAComponent, ActivateInWorldEvent>(OnActivateInWorld);

View File

@@ -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<GetOtherVerbsEvent>(AddPointingVerb);
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
CommandBinds.Builder
@@ -192,6 +195,26 @@ namespace Content.Server.Pointing.EntitySystems
.Register<PointingSystem>();
}
private void AddPointingVerb(GetOtherVerbsEvent args)
{
if (args.Hands == null)
return;
//Check if the object is already being pointed at
if (args.Target.HasComponent<PointingArrowComponent>())
return;
if (!args.User.TryGetComponent<ActorComponent>(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();

View File

@@ -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
{
/// <summary>
/// Global verb that points at an entity.
/// </summary>
[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<ActorComponent>())
{
return;
}
if (!EntitySystem.Get<PointingSystem>().InRange(user, target.Transform.Coordinates))
{
return;
}
if (target.HasComponent<PointingArrowComponent>())
{
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<PointingSystem>().TryPoint(actor.PlayerSession, target.Transform.Coordinates, target.Uid);
}
}
}

View File

@@ -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<ApcPowerReceiverComponent>();
_container = ContainerHelpers.EnsureContainer<ContainerSlot>(Owner, $"{Name}-powerCellContainer");
Container = ContainerHelpers.EnsureContainer<ContainerSlot>(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
/// </summary>
/// <param name="user"></param>
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<BaseCharger>
{
protected override void GetData(IEntity user, BaseCharger component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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<BaseCharger>
{
public override bool AlternativeInteraction => true;
protected override void GetData(IEntity user, BaseCharger component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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
/// <summary>
/// If the supplied entity should fit into the charger.
/// </summary>
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;
}

View File

@@ -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<PowerCellChargerComponent, GetAlternativeVerbsEvent>(AddEjectVerb);
SubscribeLocalEvent<PowerCellChargerComponent, GetInteractionVerbsEvent>(AddInsertVerb);
SubscribeLocalEvent<WeaponCapacitorChargerComponent, GetAlternativeVerbsEvent>(AddEjectVerb);
SubscribeLocalEvent<WeaponCapacitorChargerComponent, GetInteractionVerbsEvent>(AddInsertVerb);
}
public override void Update(float frameTime)
{
foreach (var comp in EntityManager.EntityQuery<BaseCharger>(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);
}
}
}

View File

@@ -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<BatteryComponent>();
}

View File

@@ -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<PowerCellSlotComponent>
{
public override bool AlternativeInteraction => true;
protected override void GetData(IEntity user, PowerCellSlotComponent component, VerbData data)
{
if (!component.ShowVerb || !EntitySystem.Get<ActionBlockerSystem>().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)

View File

@@ -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<PowerCellComponent, SolutionChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<PowerCellSlotComponent, GetAlternativeVerbsEvent>(AddEjectVerb);
SubscribeLocalEvent<PowerCellSlotComponent, GetInteractionVerbsEvent>(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<PowerCellComponent>() ||
!_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)

View File

@@ -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<PullableComponent>
{
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<ISharedHandsComponent>() ||
!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<SharedPullingSystem>().TryStopPull(component);
}
else
{
EntitySystem.Get<SharedPullingSystem>().TryStartPull(component.Owner, user);
}
}
}
}
}

View File

@@ -17,8 +17,8 @@ namespace Content.Server.Pulling
UpdatesAfter.Add(typeof(PhysicsSystem));
SubscribeLocalEvent<PullableComponent, PullableMoveMessage>(OnPullableMove);
SubscribeLocalEvent<PullableComponent, PullableStopMovingMessage>(OnPullableStopMove);
SubscribeLocalEvent<SharedPullableComponent, PullableMoveMessage>(OnPullableMove);
SubscribeLocalEvent<SharedPullableComponent, PullableStopMovingMessage>(OnPullableStopMove);
CommandBinds.Builder
.Bind(ContentKeyFunctions.ReleasePulledObject, InputCmdHandler.FromDelegate(HandleReleasePulledObject))

View File

@@ -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";
/// <summary>
/// Entity to replace this entity with when the current one is 'flipped'.
/// </summary>
[DataField("mirrorEntity", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string MirrorEntity = default!;
}
}

View File

@@ -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
{
/// <summary>
/// Handles verbs for the <see cref="RotatableComponent"/> and <see cref="FlippableComponent"/> components.
/// </summary>
public class RotatableSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<FlippableComponent, GetOtherVerbsEvent>(AddFlipVerb);
SubscribeLocalEvent<RotatableComponent, GetOtherVerbsEvent>(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);
}
/// <summary>
/// Replace a flippable entity with it's flipped / mirror-symmetric entity.
/// </summary>
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();
}
}
}

View File

@@ -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<FlippableComponent>
{
protected override void GetData(IEntity user, FlippableComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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);
}
}
}
}

View File

@@ -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<RotatableComponent>
{
protected override void GetData(IEntity user, RotatableComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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<RotatableComponent>
{
protected override void GetData(IEntity user, RotatableComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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));
}
}
}
}

View File

@@ -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;
}
}*/
}

Some files were not shown because too many files have changed in this diff Show More