diff --git a/Content.Client/Access/IdCardConsoleSystem.cs b/Content.Client/Access/IdCardConsoleSystem.cs
index 27577d66e2..f875204bfd 100644
--- a/Content.Client/Access/IdCardConsoleSystem.cs
+++ b/Content.Client/Access/IdCardConsoleSystem.cs
@@ -1,4 +1,4 @@
-using Content.Shared.Access;
+using Content.Shared.Access.Systems;
using JetBrains.Annotations;
namespace Content.Client.Access
diff --git a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs
index 819ac7f442..e492d7e89b 100644
--- a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs
+++ b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs
@@ -1,10 +1,9 @@
using System.Collections.Generic;
-using Content.Shared.Access;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
-using static Content.Shared.Access.SharedIdCardConsoleComponent;
+using static Content.Shared.Access.Components.SharedIdCardConsoleComponent;
namespace Content.Client.Access.UI
{
diff --git a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs
index dfb35f36ac..e835c7e6f7 100644
--- a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs
+++ b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs
@@ -8,7 +8,7 @@ using Robust.Client.UserInterface.XAML;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
-using static Content.Shared.Access.SharedIdCardConsoleComponent;
+using static Content.Shared.Access.Components.SharedIdCardConsoleComponent;
namespace Content.Client.Access.UI
{
diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs
index e5ba6ea452..81c845b3cd 100644
--- a/Content.Client/Entry/IgnoredComponents.cs
+++ b/Content.Client/Entry/IgnoredComponents.cs
@@ -46,7 +46,6 @@ namespace Content.Client.Entry
"ResearchServer",
"ResearchPointSource",
"ResearchClient",
- "IdCard",
"Access",
"AccessReader",
"IdCardConsole",
@@ -155,7 +154,6 @@ namespace Content.Client.Entry
"RefillableSolution",
"DrainableSolution",
"ExaminableSolution",
- "FitsInDispenser",
"DrawableSolution",
"InjectableSolution",
"Barotrauma",
@@ -295,7 +293,6 @@ namespace Content.Client.Entry
"BatteryCharger",
"UnpoweredFlashlight",
"Uplink",
- "PDA",
"SpawnItemsOnUse",
"AmbientOnPowered",
"Wieldable",
diff --git a/Content.Client/Items/Managers/ItemSlotManager.cs b/Content.Client/Items/Managers/ItemSlotManager.cs
index c8a6171225..83d250e792 100644
--- a/Content.Client/Items/Managers/ItemSlotManager.cs
+++ b/Content.Client/Items/Managers/ItemSlotManager.cs
@@ -80,7 +80,7 @@ namespace Content.Client.Items.Managers
}
else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld)
{
- _entityManager.EntityNetManager?.SendSystemNetworkMessage(new InteractInventorySlotEvent(item, altInteract: true));
+ _entityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(item, altInteract: true));
}
else
{
diff --git a/Content.Client/PDA/PDASystem.cs b/Content.Client/PDA/PDASystem.cs
new file mode 100644
index 0000000000..cec18d90b3
--- /dev/null
+++ b/Content.Client/PDA/PDASystem.cs
@@ -0,0 +1,10 @@
+using Content.Shared.PDA;
+using Robust.Shared.GameObjects;
+
+namespace Content.Client.PDA
+{
+ public sealed class PDASystem : SharedPDASystem
+ {
+ // Nothing here. Have a lovely day.
+ }
+}
diff --git a/Content.Client/Verbs/VerbSystem.cs b/Content.Client/Verbs/VerbSystem.cs
index 51f1a03d75..5ba5a07571 100644
--- a/Content.Client/Verbs/VerbSystem.cs
+++ b/Content.Client/Verbs/VerbSystem.cs
@@ -192,27 +192,43 @@ namespace Content.Client.Verbs
/// Execute actions associated with the given verb.
///
///
- /// Unless this is a client-exclusive verb, this will also tell the server to run the same verb. However, if the verb
- /// is disabled and has a tooltip, this function will only generate a pop-up-message instead of executing anything.
+ /// Unless this is a client-exclusive verb, this will also tell the server to run the same verb.
///
public void ExecuteVerb(EntityUid target, Verb verb, VerbType verbType)
{
- if (verb.Disabled)
- {
- if (verb.Message != null)
- _popupSystem.PopupCursor(verb.Message);
- return;
- }
-
var user = _playerManager.LocalPlayer?.ControlledEntity;
if (user == null)
return;
- ExecuteVerb(verb, user.Value, target);
-
- if (!verb.ClientExclusive)
+ // is this verb actually valid?
+ if (verb.Disabled)
{
- RaiseNetworkEvent(new ExecuteVerbEvent(target, verb, verbType));
+ // maybe send an informative pop-up message.
+ if (!string.IsNullOrWhiteSpace(verb.Message))
+ _popupSystem.PopupEntity(verb.Message, user.Value);
+
+ return;
+ }
+
+ if (verb.ClientExclusive)
+ // is this a client exclusive (gui) verb?
+ ExecuteVerb(verb, user.Value, target);
+ else
+ EntityManager.RaisePredictiveEvent(new ExecuteVerbEvent(target, verb, verbType));
+ }
+
+ public override void ExecuteVerb(Verb verb, EntityUid user, EntityUid target, bool forced = false)
+ {
+ // invoke any relevant actions
+ verb.Act?.Invoke();
+
+ // Maybe raise a local event
+ if (verb.ExecutionEventArgs != null)
+ {
+ if (verb.EventTarget.IsValid())
+ RaiseLocalEvent(verb.EventTarget, verb.ExecutionEventArgs);
+ else
+ RaiseLocalEvent(verb.ExecutionEventArgs);
}
}
diff --git a/Content.IntegrationTests/Tests/PDA/PDAExtensionsTests.cs b/Content.IntegrationTests/Tests/PDA/PDAExtensionsTests.cs
index fa3c862397..0ac5212b61 100644
--- a/Content.IntegrationTests/Tests/PDA/PDAExtensionsTests.cs
+++ b/Content.IntegrationTests/Tests/PDA/PDAExtensionsTests.cs
@@ -1,11 +1,12 @@
using System.Linq;
using System.Threading.Tasks;
-using Content.Server.Access.Components;
using Content.Server.Hands.Components;
using Content.Server.Inventory.Components;
using Content.Server.Items;
using Content.Server.PDA;
+using Content.Shared.Access.Components;
using Content.Shared.Containers.ItemSlots;
+using Content.Shared.PDA;
using NUnit.Framework;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
@@ -78,7 +79,7 @@ namespace Content.IntegrationTests.Tests.PDA
var itemSlots = sEntityManager.GetComponent(dummyPda);
sEntityManager.EntitySysManager.GetEntitySystem()
- .TryInsert(dummyPda, pdaComponent.IdSlot, pdaIdCard);
+ .TryInsert(dummyPda, pdaComponent.IdSlot, pdaIdCard, null);
var pdaContainedId = pdaComponent.ContainedID;
// The PDA in the hand should be found first
diff --git a/Content.Server/Access/Components/IdCardConsoleComponent.cs b/Content.Server/Access/Components/IdCardConsoleComponent.cs
index e31c97fedb..e505a40b16 100644
--- a/Content.Server/Access/Components/IdCardConsoleComponent.cs
+++ b/Content.Server/Access/Components/IdCardConsoleComponent.cs
@@ -4,6 +4,7 @@ using Content.Server.Access.Systems;
using Content.Server.Power.Components;
using Content.Server.UserInterface;
using Content.Shared.Access;
+using Content.Shared.Access.Components;
using Content.Shared.Containers.ItemSlots;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
@@ -115,6 +116,9 @@ namespace Content.Server.Access.Components
public void UpdateUserInterface()
{
+ if (!Initialized)
+ return;
+
IdCardConsoleBoundUserInterfaceState newState;
// this could be prettier
if (TargetIdSlot.Item is not {Valid: true} targetIdEntity)
diff --git a/Content.Server/Access/Systems/AccessReaderSystem.cs b/Content.Server/Access/Systems/AccessReaderSystem.cs
index 25c81350e5..8157646f8b 100644
--- a/Content.Server/Access/Systems/AccessReaderSystem.cs
+++ b/Content.Server/Access/Systems/AccessReaderSystem.cs
@@ -5,10 +5,10 @@ using System.Linq;
using Content.Server.Access.Components;
using Content.Server.Inventory.Components;
using Content.Server.Items;
-using Content.Server.PDA;
using Content.Shared.Access;
using Content.Shared.Hands.Components;
using Content.Shared.Inventory;
+using Content.Shared.PDA;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
diff --git a/Content.Server/Access/Systems/IdCardConsoleSystem.cs b/Content.Server/Access/Systems/IdCardConsoleSystem.cs
index 3e3740510b..235a989cea 100644
--- a/Content.Server/Access/Systems/IdCardConsoleSystem.cs
+++ b/Content.Server/Access/Systems/IdCardConsoleSystem.cs
@@ -1,5 +1,5 @@
using Content.Server.Access.Components;
-using Content.Shared.Access;
+using Content.Shared.Access.Systems;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
diff --git a/Content.Server/Access/Systems/IdCardSystem.cs b/Content.Server/Access/Systems/IdCardSystem.cs
index 3c363206cc..c1bcaff58e 100644
--- a/Content.Server/Access/Systems/IdCardSystem.cs
+++ b/Content.Server/Access/Systems/IdCardSystem.cs
@@ -1,18 +1,17 @@
-using Content.Server.Access.Components;
using Content.Server.Inventory.Components;
using Content.Server.Items;
-using Content.Server.PDA;
-using Content.Shared.Access;
using Content.Shared.Hands.Components;
using Content.Shared.Inventory;
+using Content.Shared.PDA;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using System.Diagnostics.CodeAnalysis;
-using Robust.Shared.IoC;
+using Content.Shared.Access.Components;
+using Content.Shared.Access.Systems;
namespace Content.Server.Access.Systems
{
- public class IdCardSystem : EntitySystem
+ public class IdCardSystem : SharedIdCardSystem
{
public override void Initialize()
{
diff --git a/Content.Server/Administration/Commands/SetOutfitCommand.cs b/Content.Server/Administration/Commands/SetOutfitCommand.cs
index 6bf82f8a95..31c3891a4b 100644
--- a/Content.Server/Administration/Commands/SetOutfitCommand.cs
+++ b/Content.Server/Administration/Commands/SetOutfitCommand.cs
@@ -2,10 +2,10 @@ using Content.Server.Administration.UI;
using Content.Server.EUI;
using Content.Server.Inventory.Components;
using Content.Server.Items;
-using Content.Server.PDA;
using Content.Server.Preferences.Managers;
using Content.Shared.Administration;
using Content.Shared.Inventory;
+using Content.Shared.PDA;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Server.GameObjects;
diff --git a/Content.Server/Cabinet/ItemCabinetSystem.cs b/Content.Server/Cabinet/ItemCabinetSystem.cs
index 628cf32005..b678b523fb 100644
--- a/Content.Server/Cabinet/ItemCabinetSystem.cs
+++ b/Content.Server/Cabinet/ItemCabinetSystem.cs
@@ -59,6 +59,8 @@ namespace Content.Server.Cabinet
private void OnContainerModified(EntityUid uid, ItemCabinetComponent cabinet, ContainerModifiedMessage args)
{
+ if (!cabinet.Initialized) return;
+
if (args.Container.ID == cabinet.CabinetSlot.ID)
UpdateAppearance(uid, cabinet);
}
diff --git a/Content.Server/Cargo/Components/CargoTelepadComponent.cs b/Content.Server/Cargo/Components/CargoTelepadComponent.cs
index b43937af93..8a8b1502cf 100644
--- a/Content.Server/Cargo/Components/CargoTelepadComponent.cs
+++ b/Content.Server/Cargo/Components/CargoTelepadComponent.cs
@@ -142,7 +142,7 @@ namespace Content.Server.Cargo.Components
// attempt to attach the label
if (_entMan.TryGetComponent(product, out PaperLabelComponent label))
{
- EntitySystem.Get().TryInsert(Owner, label.LabelSlot, printed);
+ EntitySystem.Get().TryInsert(Owner, label.LabelSlot, printed, null);
}
}
diff --git a/Content.Server/Chemistry/Components/ChemMasterComponent.cs b/Content.Server/Chemistry/Components/ChemMasterComponent.cs
index afba36220f..dee840b3e4 100644
--- a/Content.Server/Chemistry/Components/ChemMasterComponent.cs
+++ b/Content.Server/Chemistry/Components/ChemMasterComponent.cs
@@ -197,6 +197,8 @@ namespace Content.Server.Chemistry.Components
public void UpdateUserInterface()
{
+ if (!Initialized) return;
+
var state = GetUserInterfaceState();
UserInterface?.SetState(state);
}
diff --git a/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs b/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs
index 19c63bba8f..4de31b1bb9 100644
--- a/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs
+++ b/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs
@@ -241,6 +241,8 @@ namespace Content.Server.Chemistry.Components
public void UpdateUserInterface()
{
+ if (!Initialized) return;
+
var state = GetUserInterfaceState();
UserInterface?.SetState(state);
}
diff --git a/Content.Server/Chemistry/Components/TransformableContainerComponent.cs b/Content.Server/Chemistry/Components/TransformableContainerComponent.cs
index 69f4a527e2..1e3b821327 100644
--- a/Content.Server/Chemistry/Components/TransformableContainerComponent.cs
+++ b/Content.Server/Chemistry/Components/TransformableContainerComponent.cs
@@ -1,4 +1,5 @@
using Content.Server.Chemistry.Components.SolutionManager;
+using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
diff --git a/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs b/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs
index 87e0a96928..c335ebfbaa 100644
--- a/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs
+++ b/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs
@@ -1,4 +1,4 @@
-using Content.Server.Chemistry.Components;
+using Content.Server.Chemistry.Components;
using Content.Shared.Interaction;
using Content.Shared.Weapons.Melee;
using JetBrains.Annotations;
diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs
index 83c45375e8..c93d803727 100644
--- a/Content.Server/GameTicking/GameTicker.Spawning.cs
+++ b/Content.Server/GameTicking/GameTicker.Spawning.cs
@@ -21,6 +21,7 @@ using Content.Shared.Database;
using Content.Shared.GameTicking;
using Content.Shared.Ghost;
using Content.Shared.Inventory;
+using Content.Shared.PDA;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.Station;
diff --git a/Content.Server/GameTicking/Presets/PresetTraitorDeathMatch.cs b/Content.Server/GameTicking/Presets/PresetTraitorDeathMatch.cs
index 8e240b9bc8..b30fbf506b 100644
--- a/Content.Server/GameTicking/Presets/PresetTraitorDeathMatch.cs
+++ b/Content.Server/GameTicking/Presets/PresetTraitorDeathMatch.cs
@@ -20,6 +20,7 @@ using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Inventory;
using Content.Shared.MobState.Components;
+using Content.Shared.PDA;
using Content.Shared.Traitor.Uplink;
using Robust.Server.Player;
using Robust.Shared.Configuration;
diff --git a/Content.Server/Interaction/InteractionSystem.cs b/Content.Server/Interaction/InteractionSystem.cs
index c5542c14c3..f21ee116ef 100644
--- a/Content.Server/Interaction/InteractionSystem.cs
+++ b/Content.Server/Interaction/InteractionSystem.cs
@@ -41,19 +41,17 @@ namespace Content.Server.Interaction
{
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly PullingSystem _pullSystem = default!;
- [Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
[Dependency] private readonly AdminLogSystem _adminLogSystem = default!;
public override void Initialize()
{
+ base.Initialize();
+
SubscribeNetworkEvent(HandleDragDropRequestEvent);
- SubscribeNetworkEvent(HandleInteractInventorySlotEvent);
CommandBinds.Builder
.Bind(EngineKeyFunctions.Use,
new PointerInputCmdHandler(HandleUseInteraction))
- .Bind(ContentKeyFunctions.AltActivateItemInWorld,
- new PointerInputCmdHandler(HandleAltUseInteraction))
.Bind(ContentKeyFunctions.WideAttack,
new PointerInputCmdHandler(HandleWideAttack))
.Bind(ContentKeyFunctions.ActivateItemInWorld,
@@ -69,36 +67,6 @@ namespace Content.Server.Interaction
base.Shutdown();
}
- #region Client Input Validation
- private bool ValidateClientInput(ICommonSession? session, EntityCoordinates coords, EntityUid uid, [NotNullWhen(true)] out EntityUid? userEntity)
- {
- userEntity = null;
-
- if (!coords.IsValid(EntityManager))
- {
- Logger.InfoS("system.interaction", $"Invalid Coordinates: client={session}, coords={coords}");
- return false;
- }
-
- if (uid.IsClientSide())
- {
- Logger.WarningS("system.interaction",
- $"Client sent interaction with client-side entity. Session={session}, Uid={uid}");
- return false;
- }
-
- userEntity = ((IPlayerSession?) session)?.AttachedEntity;
-
- if (userEntity == null || !userEntity.Value.IsValid())
- {
- Logger.WarningS("system.interaction",
- $"Client sent interaction with no attached entity. Session={session}");
- return false;
- }
-
- return true;
- }
-
public override bool CanAccessViaStorage(EntityUid user, EntityUid target)
{
if (Deleted(target))
@@ -119,37 +87,6 @@ namespace Content.Server.Interaction
// we don't check if the user can access the storage entity itself. This should be handed by the UI system.
return storage.SubscribedSessions.Contains(actor.PlayerSession);
}
- #endregion
-
- ///
- /// Handles the event were a client uses an item in their inventory or in their hands, either by
- /// alt-clicking it or pressing 'E' while hovering over it.
- ///
- private void HandleInteractInventorySlotEvent(InteractInventorySlotEvent msg, EntitySessionEventArgs args)
- {
- if (Deleted(msg.ItemUid))
- {
- Logger.WarningS("system.interaction",
- $"Client sent inventory interaction with an invalid target item. Session={args.SenderSession}");
- return;
- }
-
- var itemCoords = Transform(msg.ItemUid).Coordinates;
-
- // client sanitization
- if (!ValidateClientInput(args.SenderSession, itemCoords, msg.ItemUid, out var userEntity))
- {
- Logger.InfoS("system.interaction", $"Inventory interaction validation failed. Session={args.SenderSession}");
- return;
- }
-
- if (msg.AltInteract)
- // Use 'UserInteraction' function - behaves as if the user alt-clicked the item in the world.
- UserInteraction(userEntity.Value, itemCoords, msg.ItemUid, msg.AltInteract);
- else
- // User used 'E'. We want to activate it, not simulate clicking on the item
- InteractionActivate(userEntity.Value, msg.ItemUid);
- }
#region Drag drop
private void HandleDragDropRequestEvent(DragDropRequestEvent msg, EntitySessionEventArgs args)
@@ -258,20 +195,6 @@ namespace Content.Server.Interaction
return true;
}
- public bool HandleAltUseInteraction(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
- {
- // client sanitization
- if (!ValidateClientInput(session, coords, uid, out var userEntity))
- {
- Logger.InfoS("system.interaction", $"Alt-use input validation failed");
- return true;
- }
-
- UserInteraction(userEntity.Value, coords, !Deleted(uid) ? uid : null, altInteract : true );
-
- return true;
- }
-
private bool HandleTryPullObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
if (!ValidateClientInput(session, coords, uid, out var userEntity))
@@ -295,97 +218,14 @@ namespace Content.Server.Interaction
return _pullSystem.TogglePull(userEntity.Value, pull);
}
- ///
- /// Resolves user interactions with objects.
- ///
- ///
- /// Checks Whether combat mode is enabled and whether the user can actually interact with the given entity.
- ///
- /// Whether to use default or alternative interactions (usually as a result of
- /// alt+clicking). If combat mode is enabled, the alternative action is to perform the default non-combat
- /// interaction. Having an item in the active hand also disables alternative interactions.
- public async void UserInteraction(EntityUid user, EntityCoordinates coordinates, EntityUid? target, bool altInteract = false)
- {
- // TODO COMBAT Consider using alt-interact for advanced combat? maybe alt-interact disarms?
- if (!altInteract && TryComp(user, out CombatModeComponent? combatMode) && combatMode.IsInCombatMode)
- {
- DoAttack(user, coordinates, false, target);
- return;
- }
-
- if (!ValidateInteractAndFace(user, coordinates))
- return;
-
- if (!_actionBlockerSystem.CanInteract(user))
- return;
-
- // Check if interacted entity is in the same container, the direct child, or direct parent of the user.
- // This is bypassed IF the interaction happened through an item slot (e.g., backpack UI)
- if (target != null && !Deleted(target.Value) && !user.IsInSameOrParentContainer(target.Value) && !CanAccessViaStorage(user, target.Value))
- {
- Logger.WarningS("system.interaction",
- $"User entity {ToPrettyString(user):user} clicked on object {ToPrettyString(target.Value):target} that isn't the parent, child, or in the same container");
- return;
- }
-
- // Verify user has a hand, and find what object they are currently holding in their active hand
- if (!TryComp(user, out HandsComponent? hands))
- return;
-
- var item = hands.GetActiveHand?.Owner;
-
- // TODO: Replace with body interaction range when we get something like arm length or telekinesis or something.
- var inRangeUnobstructed = user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true);
- if (target == null || Deleted(target.Value) || !inRangeUnobstructed)
- {
- if (item == null)
- return;
-
- if (!await InteractUsingRanged(user, item.Value, target, coordinates, inRangeUnobstructed) &&
- !inRangeUnobstructed)
- {
- var message = Loc.GetString("interaction-system-user-interaction-cannot-reach");
- user.PopupMessage(message);
- }
-
- return;
- }
-
- // We are close to the nearby object.
- if (altInteract)
- // Perform alternative interactions, using context menu verbs.
- AltInteract(user, target.Value);
- 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
- // currently in our hand. We will use the item in our hand on the nearby object via InteractUsing
- await InteractUsing(user, item.Value, target.Value, coordinates);
- else if (item == null)
- // Since our hand is empty we will use InteractHand/Activate
- InteractHand(user, target.Value);
- }
-
- private bool ValidateInteractAndFace(EntityUid user, EntityCoordinates coordinates)
- {
- // Verify user is on the same map as the entity they clicked on
- if (coordinates.GetMapId(EntityManager) != Transform(user).MapID)
- {
- Logger.WarningS("system.interaction",
- $"User entity {ToPrettyString(user):user} clicked on a map they aren't located on");
- return false;
- }
-
- _rotateToFaceSystem.TryFaceCoordinates(user, coordinates.ToMapPos(EntityManager));
-
- return true;
- }
-
///
/// Uses an empty hand on an entity
/// Finds components with the InteractHand interface and calls their function
/// NOTE: Does not have an InRangeUnobstructed check
///
- public void InteractHand(EntityUid user, EntityUid target)
+ public override void InteractHand(EntityUid user, EntityUid target)
{
+ // TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction.
if (!_actionBlockerSystem.CanInteract(user))
return;
@@ -416,8 +256,9 @@ namespace Content.Server.Interaction
/// Will have two behaviors, either "uses" the used entity at range on the target entity if it is capable of accepting that action
/// Or it will use the used entity itself on the position clicked, regardless of what was there
///
- public async Task InteractUsingRanged(EntityUid user, EntityUid used, EntityUid? target, EntityCoordinates clickLocation, bool inRangeUnobstructed)
+ public override async Task InteractUsingRanged(EntityUid user, EntityUid used, EntityUid? target, EntityCoordinates clickLocation, bool inRangeUnobstructed)
{
+ // TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction.
if (InteractDoBefore(user, used, inRangeUnobstructed ? target : null, clickLocation, false))
return true;
@@ -445,8 +286,9 @@ namespace Content.Server.Interaction
return await InteractDoAfter(user, used, inRangeUnobstructed ? target : null, clickLocation, false);
}
- public void DoAttack(EntityUid user, EntityCoordinates coordinates, bool wideAttack, EntityUid? targetUid = null)
+ public override void DoAttack(EntityUid user, EntityCoordinates coordinates, bool wideAttack, EntityUid? target = null)
{
+ // TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction.
if (!ValidateInteractAndFace(user, coordinates))
return;
@@ -456,10 +298,10 @@ namespace Content.Server.Interaction
if (!wideAttack)
{
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
- if (targetUid != null && !Deleted(targetUid.Value) && !user.IsInSameOrParentContainer(targetUid.Value) && !CanAccessViaStorage(user, targetUid.Value))
+ if (target != null && !Deleted(target.Value) && !user.IsInSameOrParentContainer(target.Value) && !CanAccessViaStorage(user, target.Value))
{
Logger.WarningS("system.interaction",
- $"User entity {ToPrettyString(user):user} clicked on object {ToPrettyString(targetUid.Value):target} that isn't the parent, child, or in the same container");
+ $"User entity {ToPrettyString(user):user} clicked on object {ToPrettyString(target.Value):target} that isn't the parent, child, or in the same container");
return;
}
@@ -488,15 +330,15 @@ namespace Content.Server.Interaction
}
else
{
- var ev = new ClickAttackEvent(item.Value, user, coordinates, targetUid);
+ var ev = new ClickAttackEvent(item.Value, user, coordinates, target);
RaiseLocalEvent(item.Value, ev, false);
if (ev.Handled)
{
- if (targetUid != null)
+ if (target != null)
{
_adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium,
- $"{ToPrettyString(user):user} attacked {ToPrettyString(targetUid.Value):target} with {ToPrettyString(item.Value):used} at {coordinates}");
+ $"{ToPrettyString(user):user} attacked {ToPrettyString(target.Value):target} with {ToPrettyString(item.Value):used} at {coordinates}");
}
else
{
@@ -508,10 +350,10 @@ namespace Content.Server.Interaction
}
}
}
- else if (!wideAttack && targetUid != null && HasComp(targetUid.Value))
+ else if (!wideAttack && target != null && HasComp(target.Value))
{
// We pick up items if our hand is empty, even if we're in combat mode.
- InteractHand(user, targetUid.Value);
+ InteractHand(user, target.Value);
return;
}
}
@@ -527,14 +369,14 @@ namespace Content.Server.Interaction
}
else
{
- var ev = new ClickAttackEvent(user, user, coordinates, targetUid);
+ var ev = new ClickAttackEvent(user, user, coordinates, target);
RaiseLocalEvent(user, ev, false);
if (ev.Handled)
{
- if (targetUid != null)
+ if (target != null)
{
_adminLogSystem.Add(LogType.AttackUnarmedClick, LogImpact.Medium,
- $"{ToPrettyString(user):user} attacked {ToPrettyString(targetUid.Value):target} at {coordinates}");
+ $"{ToPrettyString(user):user} attacked {ToPrettyString(target.Value):target} at {coordinates}");
}
else
{
diff --git a/Content.Server/Labels/Label/LabelSystem.cs b/Content.Server/Labels/Label/LabelSystem.cs
index 3fc98966ab..4f4621c5e0 100644
--- a/Content.Server/Labels/Label/LabelSystem.cs
+++ b/Content.Server/Labels/Label/LabelSystem.cs
@@ -85,9 +85,10 @@ namespace Content.Server.Labels
args.PushMarkup(text.TrimEnd());
}
-
private void OnContainerModified(EntityUid uid, PaperLabelComponent label, ContainerModifiedMessage args)
{
+ if (!label.Initialized) return;
+
if (args.Container.ID != label.LabelSlot.ID)
return;
diff --git a/Content.Server/Nuke/NukeSystem.cs b/Content.Server/Nuke/NukeSystem.cs
index bceb21a134..5d2cee9ce0 100644
--- a/Content.Server/Nuke/NukeSystem.cs
+++ b/Content.Server/Nuke/NukeSystem.cs
@@ -62,6 +62,9 @@ namespace Content.Server.Nuke
{
component.RemainingTime = component.Timer;
_itemSlots.AddItemSlot(uid, component.Name, component.DiskSlot);
+
+ UpdateStatus(uid, component);
+ UpdateUserInterface(uid, component);
}
public override void Update(float frameTime)
@@ -101,6 +104,8 @@ namespace Content.Server.Nuke
private void OnItemSlotChanged(EntityUid uid, NukeComponent component, ContainerModifiedMessage args)
{
+ if (!component.Initialized) return;
+
if (args.Container.ID != component.DiskSlot.ID)
return;
diff --git a/Content.Server/PDA/PDAExtensions.cs b/Content.Server/PDA/PDAExtensions.cs
index 8787dc5165..23c5ffc0ce 100644
--- a/Content.Server/PDA/PDAExtensions.cs
+++ b/Content.Server/PDA/PDAExtensions.cs
@@ -1,7 +1,8 @@
-using System.Diagnostics.CodeAnalysis;
-using Content.Server.Access.Components;
+using System.Diagnostics.CodeAnalysis;
using Content.Server.Hands.Components;
using Content.Server.Inventory.Components;
+using Content.Shared.Access.Components;
+using Content.Shared.PDA;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
diff --git a/Content.Server/PDA/PDASystem.cs b/Content.Server/PDA/PDASystem.cs
index 5c95c183d3..903f868ee8 100644
--- a/Content.Server/PDA/PDASystem.cs
+++ b/Content.Server/PDA/PDASystem.cs
@@ -1,4 +1,3 @@
-using Content.Server.Access.Components;
using Content.Server.Light.Components;
using Content.Server.Light.EntitySystems;
using Content.Server.Light.Events;
@@ -15,9 +14,8 @@ using Robust.Shared.IoC;
namespace Content.Server.PDA
{
- public class PDASystem : EntitySystem
+ public sealed class PDASystem : SharedPDASystem
{
- [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
[Dependency] private readonly UplinkSystem _uplinkSystem = default!;
[Dependency] private readonly UnpoweredFlashlightSystem _unpoweredFlashlight = default!;
@@ -25,36 +23,21 @@ namespace Content.Server.PDA
{
base.Initialize();
- SubscribeLocalEvent(OnComponentInit);
- SubscribeLocalEvent(OnComponentRemove);
-
SubscribeLocalEvent(OnActivateInWorld);
SubscribeLocalEvent(OnUse);
- SubscribeLocalEvent(OnItemInserted);
- SubscribeLocalEvent(OnItemRemoved);
SubscribeLocalEvent(OnLightToggle);
-
- SubscribeLocalEvent(OnUplinkInit);
- SubscribeLocalEvent(OnUplinkRemoved);
}
- private void OnComponentInit(EntityUid uid, PDAComponent pda, ComponentInit args)
+ protected override void OnComponentInit(EntityUid uid, PDAComponent pda, ComponentInit args)
{
+ base.OnComponentInit(uid, pda, args);
+
var ui = pda.Owner.GetUIOrNull(PDAUiKey.Key);
if (ui != null)
ui.OnReceiveMessage += (msg) => OnUIMessage(pda, msg);
-
- if (pda.IdCard != null)
- pda.IdSlot.StartingItem = pda.IdCard;
- _itemSlotsSystem.AddItemSlot(uid, $"{pda.Name}-id", pda.IdSlot);
- _itemSlotsSystem.AddItemSlot(uid, $"{pda.Name}-pen", pda.PenSlot);
}
- private void OnComponentRemove(EntityUid uid, PDAComponent pda, ComponentRemove args)
- {
- _itemSlotsSystem.RemoveItemSlot(uid, pda.IdSlot);
- _itemSlotsSystem.RemoveItemSlot(uid, pda.PenSlot);
- }
+
private void OnUse(EntityUid uid, PDAComponent pda, UseInHandEvent args)
{
@@ -70,21 +53,15 @@ namespace Content.Server.PDA
args.Handled = OpenUI(pda, args.User);
}
- private void OnItemInserted(EntityUid uid, PDAComponent pda, EntInsertedIntoContainerMessage args)
+ protected override void OnItemInserted(EntityUid uid, PDAComponent pda, EntInsertedIntoContainerMessage args)
{
- if (args.Container.ID == pda.IdSlot.ID)
- pda.ContainedID = EntityManager.GetComponentOrNull(args.Entity);
-
- UpdatePDAAppearance(pda);
+ base.OnItemInserted(uid, pda, args);
UpdatePDAUserInterface(pda);
}
- private void OnItemRemoved(EntityUid uid, PDAComponent pda, EntRemovedFromContainerMessage args)
+ protected override void OnItemRemoved(EntityUid uid, PDAComponent pda, EntRemovedFromContainerMessage args)
{
- if (args.Container.ID == pda.IdSlot.ID)
- pda.ContainedID = null;
-
- UpdatePDAAppearance(pda);
+ base.OnItemRemoved(uid, pda, args);
UpdatePDAUserInterface(pda);
}
@@ -162,12 +139,12 @@ namespace Content.Server.PDA
case PDAEjectIDMessage _:
{
- _itemSlotsSystem.TryEjectToHands(pda.Owner, pda.IdSlot, playerUid);
+ ItemSlotsSystem.TryEjectToHands(pda.Owner, pda.IdSlot, playerUid);
break;
}
case PDAEjectPenMessage _:
{
- _itemSlotsSystem.TryEjectToHands(pda.Owner, pda.PenSlot, playerUid);
+ ItemSlotsSystem.TryEjectToHands(pda.Owner, pda.PenSlot, playerUid);
break;
}
case PDAShowUplinkMessage _:
diff --git a/Content.Server/Sandbox/SandboxManager.cs b/Content.Server/Sandbox/SandboxManager.cs
index 572dc5f4cb..4c78ce12d6 100644
--- a/Content.Server/Sandbox/SandboxManager.cs
+++ b/Content.Server/Sandbox/SandboxManager.cs
@@ -5,9 +5,10 @@ using Content.Server.GameTicking;
using Content.Server.Hands.Components;
using Content.Server.Inventory.Components;
using Content.Server.Items;
-using Content.Server.PDA;
using Content.Shared.Access;
+using Content.Shared.Access.Components;
using Content.Shared.Containers.ItemSlots;
+using Content.Shared.PDA;
using Content.Shared.Sandbox;
using Robust.Server.Console;
using Robust.Server.GameObjects;
@@ -139,7 +140,7 @@ namespace Content.Server.Sandbox
if (_entityManager.TryGetComponent(pda.Owner, out ItemSlotsComponent? itemSlots))
{
_entityManager.EntitySysManager.GetEntitySystem().
- TryInsert(wornItem.Owner, pda.IdSlot, newID);
+ TryInsert(wornItem.Owner, pda.IdSlot, newID, null);
}
}
else
diff --git a/Content.Server/Traitor/Uplink/UplinkSystem.cs b/Content.Server/Traitor/Uplink/UplinkSystem.cs
index 458defd0e2..2e3bcb2915 100644
--- a/Content.Server/Traitor/Uplink/UplinkSystem.cs
+++ b/Content.Server/Traitor/Uplink/UplinkSystem.cs
@@ -2,13 +2,13 @@ using System.Linq;
using Content.Server.Hands.Components;
using Content.Server.Inventory.Components;
using Content.Server.Items;
-using Content.Server.PDA;
using Content.Server.Traitor.Uplink.Account;
using Content.Server.Traitor.Uplink.Components;
using Content.Server.UserInterface;
using Content.Shared.ActionBlocker;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
+using Content.Shared.PDA;
using Content.Shared.Traitor.Uplink;
using Robust.Server.GameObjects;
using Robust.Server.Player;
diff --git a/Content.Server/Verbs/VerbSystem.cs b/Content.Server/Verbs/VerbSystem.cs
index 369ae31ae8..83d8a43886 100644
--- a/Content.Server/Verbs/VerbSystem.cs
+++ b/Content.Server/Verbs/VerbSystem.cs
@@ -1,53 +1,26 @@
+using Content.Server.Popups;
+using Content.Shared.Administration.Logs;
+using Content.Shared.Database;
+using Content.Shared.Hands.Components;
using Content.Shared.Verbs;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
using Robust.Shared.Log;
+using Robust.Shared.Player;
namespace Content.Server.Verbs
{
public sealed class VerbSystem : SharedVerbSystem
{
+ [Dependency] private readonly SharedAdminLogSystem _logSystem = default!;
+ [Dependency] private readonly PopupSystem _popupSystem = default!;
+
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent(HandleVerbRequest);
- SubscribeNetworkEvent(HandleTryExecuteVerb);
- }
-
- ///
- /// Called when asked over the network to run a given verb.
- ///
- public void HandleTryExecuteVerb(ExecuteVerbEvent args, EntitySessionEventArgs eventArgs)
- {
- var session = eventArgs.SenderSession;
-
- if (session.AttachedEntity is not {} userEntity)
- {
- Logger.Warning($"{nameof(HandleTryExecuteVerb)} called by player {session} with no attached entity.");
- return;
- }
-
- if (!EntityManager.EntityExists(args.Target))
- {
- return;
- }
-
- // Get the list of verbs. This effectively also checks that the requested verb is in fact a valid verb that
- // the user can perform.
- var verbs = GetLocalVerbs(args.Target, userEntity, args.Type)[args.Type];
-
- // Note that GetLocalVerbs might waste time checking & preparing unrelated verbs even though we know
- // precisely which one we want to run. However, MOST entities will only have 1 or 2 verbs of a given type.
- // The one exception here is the "other" verb type, which has 3-4 verbs + all the debug verbs.
-
- // Find the requested verb.
- if (verbs.TryGetValue(args.RequestedVerb, out var verb))
- ExecuteVerb(verb, userEntity, args.Target);
- else
- // 404 Verb not found. Note that this could happen due to something as simple as opening the verb menu, walking away, then trying
- // to run the pickup-item verb. So maybe this shouldn't even be logged?
- Logger.Info($"{nameof(HandleTryExecuteVerb)} called by player {session} with an invalid verb: {args.RequestedVerb.Category?.Text} {args.RequestedVerb.Text}");
}
private void HandleVerbRequest(RequestServerVerbsEvent args, EntitySessionEventArgs eventArgs)
@@ -73,5 +46,70 @@ namespace Content.Server.Verbs
var response = new VerbsResponseEvent(args.EntityUid, GetLocalVerbs(args.EntityUid, attached, args.Type));
RaiseNetworkEvent(response, player.ConnectedClient);
}
+
+ ///
+ /// Execute the provided verb.
+ ///
+ ///
+ /// This will try to call the action delegates and raise the local events for the given verb.
+ ///
+ public override void ExecuteVerb(Verb verb, EntityUid user, EntityUid target, bool forced = false)
+ {
+ // is this verb actually valid?
+ if (verb.Disabled)
+ {
+ // Send an informative pop-up message
+ if (!string.IsNullOrWhiteSpace(verb.Message))
+ _popupSystem.PopupEntity(verb.Message, user, Filter.Entities(user));
+
+ return;
+ }
+
+ // first, lets log the verb. Just in case it ends up crashing the server or something.
+ LogVerb(verb, user, target, forced);
+
+ // then invoke any relevant actions
+ verb.Act?.Invoke();
+
+ // Maybe raise a local event
+ if (verb.ExecutionEventArgs != null)
+ {
+ if (verb.EventTarget.IsValid())
+ RaiseLocalEvent(verb.EventTarget, verb.ExecutionEventArgs);
+ else
+ RaiseLocalEvent(verb.ExecutionEventArgs);
+ }
+ }
+
+ public void LogVerb(Verb verb, EntityUid user, EntityUid target, bool forced)
+ {
+ // first get the held item. again.
+ EntityUid? holding = null;
+ if (TryComp(user, out SharedHandsComponent? hands) &&
+ hands.TryGetActiveHeldEntity(out var heldEntity))
+ {
+ holding = heldEntity;
+ }
+
+ // if this is a virtual pull, get the held entity
+ if (holding != null && TryComp(holding, out HandVirtualItemComponent? pull))
+ holding = pull.BlockingEntity;
+
+ var verbText = $"{verb.Category?.Text} {verb.Text}".Trim();
+
+ // lets not frame people, eh?
+ var executionText = forced ? "was forced to execute" : "executed";
+
+ if (holding == null)
+ {
+ _logSystem.Add(LogType.Verb, verb.Impact,
+ $"{ToPrettyString(user):user} {executionText} the [{verbText:verb}] verb targeting {ToPrettyString(target):target}");
+ }
+ else
+ {
+ _logSystem.Add(LogType.Verb, verb.Impact,
+ $"{ToPrettyString(user):user} {executionText} the [{verbText:verb}] verb targeting {ToPrettyString(target):target} while holding {ToPrettyString(holding.Value):held}");
+ }
+ }
}
}
diff --git a/Content.Server/Access/Components/IdCardComponent.cs b/Content.Shared/Access/Components/IdCardComponent.cs
similarity index 60%
rename from Content.Server/Access/Components/IdCardComponent.cs
rename to Content.Shared/Access/Components/IdCardComponent.cs
index 7e0f15a8ec..be555c3eda 100644
--- a/Content.Server/Access/Components/IdCardComponent.cs
+++ b/Content.Shared/Access/Components/IdCardComponent.cs
@@ -1,13 +1,15 @@
-using Content.Server.Access.Systems;
-using Content.Server.PDA;
+using Content.Shared.Access.Systems;
+using Content.Shared.PDA;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
-namespace Content.Server.Access.Components
+namespace Content.Shared.Access.Components
{
+ // TODO BUI NETWORKING if ever clients can open their own BUI's (id card console, pda), then this data should be
+ // networked.
[RegisterComponent]
- [Friend(typeof(IdCardSystem), typeof(PDASystem))]
+ [Friend(typeof(SharedIdCardSystem), typeof(SharedPDASystem))]
public class IdCardComponent : Component
{
public override string Name => "IdCard";
diff --git a/Content.Shared/Access/SharedIdCardConsoleComponent.cs b/Content.Shared/Access/Components/SharedIdCardConsoleComponent.cs
similarity index 98%
rename from Content.Shared/Access/SharedIdCardConsoleComponent.cs
rename to Content.Shared/Access/Components/SharedIdCardConsoleComponent.cs
index 5b394a1554..0c7903ce28 100644
--- a/Content.Shared/Access/SharedIdCardConsoleComponent.cs
+++ b/Content.Shared/Access/Components/SharedIdCardConsoleComponent.cs
@@ -5,7 +5,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
-namespace Content.Shared.Access
+namespace Content.Shared.Access.Components
{
public class SharedIdCardConsoleComponent : Component
{
diff --git a/Content.Shared/Access/SharedIdCardConsoleSystem.cs b/Content.Shared/Access/Systems/SharedIdCardConsoleSystem.cs
similarity index 93%
rename from Content.Shared/Access/SharedIdCardConsoleSystem.cs
rename to Content.Shared/Access/Systems/SharedIdCardConsoleSystem.cs
index ba9decb18f..cdd34f155f 100644
--- a/Content.Shared/Access/SharedIdCardConsoleSystem.cs
+++ b/Content.Shared/Access/Systems/SharedIdCardConsoleSystem.cs
@@ -1,9 +1,10 @@
+using Content.Shared.Access.Components;
using Content.Shared.Containers.ItemSlots;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
-namespace Content.Shared.Access
+namespace Content.Shared.Access.Systems
{
[UsedImplicitly]
public abstract class SharedIdCardConsoleSystem : EntitySystem
diff --git a/Content.Shared/Access/Systems/SharedIdCardSystem.cs b/Content.Shared/Access/Systems/SharedIdCardSystem.cs
new file mode 100644
index 0000000000..f612d29a0c
--- /dev/null
+++ b/Content.Shared/Access/Systems/SharedIdCardSystem.cs
@@ -0,0 +1,8 @@
+using Robust.Shared.GameObjects;
+
+namespace Content.Shared.Access.Systems;
+
+public abstract class SharedIdCardSystem : EntitySystem
+{
+ // this class just exists to make friends. Will you be its friend?
+}
diff --git a/Content.Server/Chemistry/Components/SolutionManager/FitsInDispenserComponent.cs b/Content.Shared/Chemistry/Components/FitsInDispenserComponent.cs
similarity index 75%
rename from Content.Server/Chemistry/Components/SolutionManager/FitsInDispenserComponent.cs
rename to Content.Shared/Chemistry/Components/FitsInDispenserComponent.cs
index 4fada63168..8d7db5ca93 100644
--- a/Content.Server/Chemistry/Components/SolutionManager/FitsInDispenserComponent.cs
+++ b/Content.Shared/Chemistry/Components/FitsInDispenserComponent.cs
@@ -1,15 +1,17 @@
-using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects;
+using Robust.Shared.GameStates;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
-namespace Content.Server.Chemistry.Components.SolutionManager
+namespace Content.Shared.Chemistry.Components
{
/// Allows the entity with this component to be placed in a SharedReagentDispenserComponent.
/// Otherwise it's considered to be too large or the improper shape to fit.
/// Allows us to have obscenely large containers that are harder to abuse in chem dispensers
/// since they can't be placed directly in them.
- ///
+ ///
[RegisterComponent]
+ [NetworkedComponent] // only needed for white-lists. Client doesn't actually need Solution data;
public class FitsInDispenserComponent : Component
{
public override string Name => "FitsInDispenser";
diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs
index 765a838ba6..d93ba287df 100644
--- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs
+++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs
@@ -3,6 +3,7 @@ using Content.Shared.Acts;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.Popups;
+using Content.Shared.Sound;
using Content.Shared.Verbs;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
@@ -12,6 +13,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Player;
+using Robust.Shared.Timing;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -24,6 +26,7 @@ namespace Content.Shared.Containers.ItemSlots
{
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
public override void Initialize()
{
@@ -174,7 +177,7 @@ namespace Content.Shared.Containers.ItemSlots
if (slot.Item != null)
hands.TryPutInAnyHand(slot.Item.Value);
- Insert(uid, slot, args.Used);
+ Insert(uid, slot, args.Used, args.User, true);
args.Handled = true;
return;
}
@@ -186,13 +189,32 @@ namespace Content.Shared.Containers.ItemSlots
/// Insert an item into a slot. This does not perform checks, so make sure to also use or just use instead.
///
- private void Insert(EntityUid uid, ItemSlot slot, EntityUid item)
+ private void Insert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user, bool userAudio = false)
{
slot.ContainerSlot.Insert(item);
// ContainerSlot automatically raises a directed EntInsertedIntoContainerMessage
- if (slot.InsertSound != null)
- SoundSystem.Play(Filter.Pvs(uid), slot.InsertSound.GetSound(), uid, slot.SoundOptions);
+ PlaySound(uid, slot.InsertSound, slot.SoundOptions, userAudio ? null : user);
+ }
+
+ ///
+ /// Plays a sound
+ ///
+ /// Source of the sound
+ /// The sound
+ /// Optional (server-side) argument used to prevent sending the audio to a specific
+ /// user.
+ private void PlaySound(EntityUid uid, SoundSpecifier? sound, AudioParams audioParams, EntityUid? excluded)
+ {
+ if (sound == null || !_gameTiming.IsFirstTimePredicted)
+ return;
+
+ var filter = Filter.Pvs(uid);
+
+ if (excluded != null)
+ filter = filter.RemoveWhereAttachedEntity(entity => entity == excluded.Value);
+
+ SoundSystem.Play(filter, sound.GetSound(), uid, audioParams);
}
///
@@ -228,7 +250,7 @@ namespace Content.Shared.Containers.ItemSlots
/// Tries to insert item into a specific slot.
///
/// False if failed to insert item
- public bool TryInsert(EntityUid uid, string id, EntityUid item, ItemSlotsComponent? itemSlots = null)
+ public bool TryInsert(EntityUid uid, string id, EntityUid item, EntityUid? user, ItemSlotsComponent? itemSlots = null)
{
if (!Resolve(uid, ref itemSlots))
return false;
@@ -236,19 +258,19 @@ namespace Content.Shared.Containers.ItemSlots
if (!itemSlots.Slots.TryGetValue(id, out var slot))
return false;
- return TryInsert(uid, slot, item);
+ return TryInsert(uid, slot, item, user);
}
///
/// Tries to insert item into a specific slot.
///
/// False if failed to insert item
- public bool TryInsert(EntityUid uid, ItemSlot slot, EntityUid item)
+ public bool TryInsert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user)
{
if (!CanInsert(uid, item, slot))
return false;
- Insert(uid, slot, item);
+ Insert(uid, slot, item, user);
return true;
}
@@ -271,7 +293,7 @@ namespace Content.Shared.Containers.ItemSlots
if (!_actionBlockerSystem.CanInteract(user) && hands.Drop(item))
return false;
- Insert(uid, slot, item);
+ Insert(uid, slot, item, user);
return true;
}
#endregion
@@ -281,20 +303,19 @@ namespace Content.Shared.Containers.ItemSlots
/// Eject an item into a slot. This does not perform checks (e.g., is the slot locked?), so you should
/// probably just use instead.
///
- private void Eject(EntityUid uid, ItemSlot slot, EntityUid item)
+ private void Eject(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user)
{
slot.ContainerSlot.Remove(item);
// ContainerSlot automatically raises a directed EntRemovedFromContainerMessage
- if (slot.EjectSound != null)
- SoundSystem.Play(Filter.Pvs(uid), slot.EjectSound.GetSound(), uid, slot.SoundOptions);
+ PlaySound(uid, slot.EjectSound, slot.SoundOptions, user);
}
///
/// Try to eject an item from a slot.
///
/// False if item slot is locked or has no item inserted
- public bool TryEject(EntityUid uid, ItemSlot slot, [NotNullWhen(true)] out EntityUid? item)
+ public bool TryEject(EntityUid uid, ItemSlot slot, EntityUid? user, [NotNullWhen(true)] out EntityUid? item)
{
item = null;
@@ -302,7 +323,7 @@ namespace Content.Shared.Containers.ItemSlots
return false;
item = slot.Item;
- Eject(uid, slot, item.Value);
+ Eject(uid, slot, item.Value, user);
return true;
}
@@ -310,7 +331,8 @@ namespace Content.Shared.Containers.ItemSlots
/// Try to eject item from a slot.
///
/// False if the id is not valid, the item slot is locked, or it has no item inserted
- public bool TryEject(EntityUid uid, string id, [NotNullWhen(true)] out EntityUid? item, ItemSlotsComponent? itemSlots = null)
+ public bool TryEject(EntityUid uid, string id, EntityUid user,
+ [NotNullWhen(true)] out EntityUid? item, ItemSlotsComponent? itemSlots = null)
{
item = null;
@@ -320,7 +342,7 @@ namespace Content.Shared.Containers.ItemSlots
if (!itemSlots.Slots.TryGetValue(id, out var slot))
return false;
- return TryEject(uid, slot, out item);
+ return TryEject(uid, slot, user, out item);
}
///
@@ -333,7 +355,7 @@ namespace Content.Shared.Containers.ItemSlots
///
public bool TryEjectToHands(EntityUid uid, ItemSlot slot, EntityUid? user)
{
- if (!TryEject(uid, slot, out var item))
+ if (!TryEject(uid, slot, user, out var item))
return false;
if (user != null && EntityManager.TryGetComponent(user.Value, out SharedHandsComponent? hands))
@@ -428,7 +450,7 @@ namespace Content.Shared.Containers.ItemSlots
: EntityManager.GetComponent(args.Using.Value).EntityName ?? string.Empty;
Verb insertVerb = new();
- insertVerb.Act = () => Insert(uid, slot, args.Using.Value);
+ insertVerb.Act = () => Insert(uid, slot, args.Using.Value, args.User);
if (slot.InsertVerbText != null)
{
@@ -461,7 +483,7 @@ namespace Content.Shared.Containers.ItemSlots
foreach (var slot in component.Slots.Values)
{
if (slot.EjectOnBreak && slot.HasItem)
- TryEject(uid, slot, out var _);
+ TryEject(uid, slot, null, out var _);
}
}
diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs
index 1c5c1394a7..3b64493d67 100644
--- a/Content.Shared/Interaction/SharedInteractionSystem.cs
+++ b/Content.Shared/Interaction/SharedInteractionSystem.cs
@@ -1,12 +1,16 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Content.Shared.ActionBlocker;
using Content.Shared.Administration.Logs;
+using Content.Shared.CombatMode;
using Content.Shared.Database;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
+using Content.Shared.Input;
+using Content.Shared.Interaction.Helpers;
using Content.Shared.Inventory;
using Content.Shared.Physics;
using Content.Shared.Popups;
@@ -16,13 +20,14 @@ using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
+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.Physics;
using Robust.Shared.Players;
-using Robust.Shared.Random;
using Robust.Shared.Serialization;
#pragma warning disable 618
@@ -39,12 +44,166 @@ namespace Content.Shared.Interaction
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly SharedVerbSystem _verbSystem = default!;
[Dependency] private readonly SharedAdminLogSystem _adminLogSystem = default!;
+ [Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
public const float InteractionRange = 2;
public const float InteractionRangeSquared = InteractionRange * InteractionRange;
public delegate bool Ignored(EntityUid entity);
+ public override void Initialize()
+ {
+ SubscribeAllEvent(HandleInteractInventorySlotEvent);
+
+ CommandBinds.Builder
+ .Bind(ContentKeyFunctions.AltActivateItemInWorld,
+ new PointerInputCmdHandler(HandleAltUseInteraction))
+ .Register();
+ }
+
+ public override void Shutdown()
+ {
+ CommandBinds.Unregister();
+ base.Shutdown();
+ }
+
+ ///
+ /// Handles the event were a client uses an item in their inventory or in their hands, either by
+ /// alt-clicking it or pressing 'E' while hovering over it.
+ ///
+ private void HandleInteractInventorySlotEvent(InteractInventorySlotEvent msg, EntitySessionEventArgs args)
+ {
+ var coords = Transform(msg.ItemUid).Coordinates;
+ // client sanitization
+ if (!ValidateClientInput(args.SenderSession, coords, msg.ItemUid, out var user))
+ {
+ Logger.InfoS("system.interaction", $"Inventory interaction validation failed. Session={args.SenderSession}");
+ return;
+ }
+
+ if (msg.AltInteract)
+ // Use 'UserInteraction' function - behaves as if the user alt-clicked the item in the world.
+ UserInteraction(user.Value, coords, msg.ItemUid, msg.AltInteract);
+ else
+ // User used 'E'. We want to activate it, not simulate clicking on the item
+ InteractionActivate(user.Value, msg.ItemUid);
+ }
+
+ public bool HandleAltUseInteraction(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
+ {
+ // client sanitization
+ if (!ValidateClientInput(session, coords, uid, out var user))
+ {
+ Logger.InfoS("system.interaction", $"Alt-use input validation failed");
+ return true;
+ }
+
+ UserInteraction(user.Value, coords, uid, altInteract: true);
+
+ return false;
+ }
+
+ ///
+ /// Resolves user interactions with objects.
+ ///
+ ///
+ /// Checks Whether combat mode is enabled and whether the user can actually interact with the given entity.
+ ///
+ /// Whether to use default or alternative interactions (usually as a result of
+ /// alt+clicking). If combat mode is enabled, the alternative action is to perform the default non-combat
+ /// interaction. Having an item in the active hand also disables alternative interactions.
+ public async void UserInteraction(EntityUid user, EntityCoordinates coordinates, EntityUid? target, bool altInteract = false)
+ {
+ if (target != null && Deleted(target.Value))
+ return;
+
+ // TODO COMBAT Consider using alt-interact for advanced combat? maybe alt-interact disarms?
+ if (!altInteract && TryComp(user, out SharedCombatModeComponent? combatMode) && combatMode.IsInCombatMode)
+ {
+ DoAttack(user, coordinates, false, target);
+ return;
+ }
+
+ if (!ValidateInteractAndFace(user, coordinates))
+ return;
+
+ if (!_actionBlockerSystem.CanInteract(user))
+ return;
+
+ // Check if interacted entity is in the same container, the direct child, or direct parent of the user.
+ // This is bypassed IF the interaction happened through an item slot (e.g., backpack UI)
+ if (target != null && !user.IsInSameOrParentContainer(target.Value) && !CanAccessViaStorage(user, target.Value))
+ return;
+
+ // Verify user has a hand, and find what object they are currently holding in their active hand
+ if (!TryComp(user, out SharedHandsComponent? hands))
+ return;
+
+ // TODO remove invalid/default uid and use nullable.
+ hands.TryGetActiveHeldEntity(out var heldEntity);
+ EntityUid? held = heldEntity.Valid ? heldEntity : null;
+
+ // TODO: Replace with body interaction range when we get something like arm length or telekinesis or something.
+ var inRangeUnobstructed = user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true);
+ if (target == null || !inRangeUnobstructed)
+ {
+ if (held == null)
+ return;
+
+ if (!await InteractUsingRanged(user, held.Value, target, coordinates, inRangeUnobstructed) &&
+ !inRangeUnobstructed)
+ {
+ var message = Loc.GetString("interaction-system-user-interaction-cannot-reach");
+ user.PopupMessage(message);
+ }
+
+ return;
+ }
+ else
+ {
+ // We are close to the nearby object.
+ if (altInteract)
+ // Perform alternative interactions, using context menu verbs.
+ AltInteract(user, target.Value);
+ else if (held != null && held != target)
+ // We are performing a standard interaction with an item, and the target isn't the same as the item
+ // currently in our hand. We will use the item in our hand on the nearby object via InteractUsing
+ await InteractUsing(user, held.Value, target.Value, coordinates);
+ else if (held == null)
+ // Since our hand is empty we will use InteractHand/Activate
+ InteractHand(user, target.Value);
+ }
+ }
+
+ public virtual void InteractHand(EntityUid user, EntityUid target)
+ {
+ // TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction.
+ }
+
+ public virtual void DoAttack(EntityUid user, EntityCoordinates coordinates, bool wideAttack,
+ EntityUid? targetUid = null)
+ {
+ // TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction.
+ }
+
+ public virtual async Task InteractUsingRanged(EntityUid user, EntityUid used, EntityUid? target,
+ EntityCoordinates clickLocation, bool inRangeUnobstructed)
+ {
+ // TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction.
+ return await Task.FromResult(true);
+ }
+
+ protected bool ValidateInteractAndFace(EntityUid user, EntityCoordinates coordinates)
+ {
+ // Verify user is on the same map as the entity they clicked on
+ if (coordinates.GetMapId(EntityManager) != Transform(user).MapID)
+ return false;
+
+ _rotateToFaceSystem.TryFaceCoordinates(user, coordinates.ToMapPos(EntityManager));
+
+ return true;
+ }
+
///
/// Traces a ray from coords to otherCoords and returns the length
/// of the vector between coords and the ray's first hit.
@@ -154,7 +313,7 @@ namespace Content.Shared.Interaction
foreach (var result in rayResults)
{
- if (!EntityManager.TryGetComponent(result.HitEntity, out IPhysBody? p))
+ if (!TryComp(result.HitEntity, out IPhysBody? p))
{
continue;
}
@@ -213,7 +372,7 @@ namespace Content.Shared.Interaction
bool popup = false)
{
predicate ??= e => e == origin || e == other;
- return InRangeUnobstructed(origin, EntityManager.GetComponent(other).MapPosition, range, collisionMask, predicate, ignoreInsideBlocker, popup);
+ return InRangeUnobstructed(origin, Transform(other).MapPosition, range, collisionMask, predicate, ignoreInsideBlocker, popup);
}
///
@@ -345,7 +504,7 @@ namespace Content.Shared.Interaction
bool ignoreInsideBlocker = false,
bool popup = false)
{
- var originPosition = EntityManager.GetComponent(origin).MapPosition;
+ var originPosition = Transform(origin).MapPosition;
predicate ??= e => e == origin;
var inRange = InRangeUnobstructed(originPosition, other, range, collisionMask, predicate, ignoreInsideBlocker);
@@ -392,7 +551,7 @@ namespace Content.Shared.Interaction
var interactUsingEventArgs = new InteractUsingEventArgs(user, clickLocation, used, target);
- var interactUsings = EntityManager.GetComponents(target).OrderByDescending(x => x.Priority);
+ var interactUsings = AllComps(target).OrderByDescending(x => x.Priority);
foreach (var interactUsing in interactUsings)
{
// If an InteractUsing returns a status completion we finish our interaction
@@ -418,7 +577,7 @@ namespace Content.Shared.Interaction
return true;
var afterInteractEventArgs = new AfterInteractEventArgs(user, clickLocation, target, canReach);
- var afterInteracts = EntityManager.GetComponents(used).OrderByDescending(x => x.Priority).ToList();
+ var afterInteracts = AllComps(used).OrderByDescending(x => x.Priority).ToList();
foreach (var afterInteract in afterInteracts)
{
@@ -444,7 +603,7 @@ namespace Content.Shared.Interaction
protected void InteractionActivate(EntityUid user, EntityUid used)
{
- if (EntityManager.TryGetComponent(used, out var delayComponent))
+ if (TryComp(used, out UseDelayComponent? delayComponent))
{
if (delayComponent.ActiveDelay)
return;
@@ -472,7 +631,7 @@ namespace Content.Shared.Interaction
return;
}
- if (!EntityManager.TryGetComponent(used, out IActivate? activateComp))
+ if (!TryComp(used, out IActivate? activateComp))
return;
var activateEventArgs = new ActivateEventArgs(user, used);
@@ -506,7 +665,7 @@ namespace Content.Shared.Interaction
///
public void UseInteraction(EntityUid user, EntityUid used)
{
- if (EntityManager.TryGetComponent(used, out var delayComponent))
+ if (TryComp(used, out UseDelayComponent? delayComponent))
{
if (delayComponent.ActiveDelay)
return;
@@ -519,7 +678,7 @@ namespace Content.Shared.Interaction
if (useMsg.Handled)
return;
- var uses = EntityManager.GetComponents(used).ToList();
+ var uses = AllComps(used).ToList();
// Try to use item on any components which have the interface
foreach (var use in uses)
@@ -560,7 +719,7 @@ namespace Content.Shared.Interaction
return;
}
- var comps = EntityManager.GetComponents(thrown).ToList();
+ var comps = AllComps(thrown).ToList();
var args = new ThrownEventArgs(user);
// Call Thrown on all components that implement the interface
@@ -584,7 +743,7 @@ namespace Content.Shared.Interaction
if (equipMsg.Handled)
return;
- var comps = EntityManager.GetComponents(equipped).ToList();
+ var comps = AllComps(equipped).ToList();
// Call Thrown on all components that implement the interface
foreach (var comp in comps)
@@ -604,7 +763,7 @@ namespace Content.Shared.Interaction
if (unequipMsg.Handled)
return;
- var comps = EntityManager.GetComponents(equipped).ToList();
+ var comps = AllComps(equipped).ToList();
// Call Thrown on all components that implement the interface
foreach (var comp in comps)
@@ -625,7 +784,7 @@ namespace Content.Shared.Interaction
if (equippedHandMessage.Handled)
return;
- var comps = EntityManager.GetComponents(item).ToList();
+ var comps = AllComps(item).ToList();
foreach (var comp in comps)
{
@@ -644,7 +803,7 @@ namespace Content.Shared.Interaction
if (unequippedHandMessage.Handled)
return;
- var comps = EntityManager.GetComponents(item).ToList();
+ var comps = AllComps(item).ToList();
foreach (var comp in comps)
{
@@ -681,9 +840,9 @@ namespace Content.Shared.Interaction
return;
}
- EntityManager.GetComponent(item).LocalRotation = Angle.Zero;
+ Transform(item).LocalRotation = Angle.Zero;
- var comps = EntityManager.GetComponents(item).ToList();
+ var comps = AllComps(item).ToList();
// Call Land on all components that implement the interface
foreach (var comp in comps)
@@ -706,7 +865,7 @@ namespace Content.Shared.Interaction
if (handSelectedMsg.Handled)
return;
- var comps = EntityManager.GetComponents(item).ToList();
+ var comps = AllComps(item).ToList();
// Call Land on all components that implement the interface
foreach (var comp in comps)
@@ -726,7 +885,7 @@ namespace Content.Shared.Interaction
if (handDeselectedMsg.Handled)
return;
- var comps = EntityManager.GetComponents(item).ToList();
+ var comps = AllComps(item).ToList();
// Call Land on all components that implement the interface
foreach (var comp in comps)
@@ -742,6 +901,36 @@ namespace Content.Shared.Interaction
///
public abstract bool CanAccessViaStorage(EntityUid user, EntityUid target);
+ protected bool ValidateClientInput(ICommonSession? session, EntityCoordinates coords,
+ EntityUid uid, [NotNullWhen(true)] out EntityUid? userEntity)
+ {
+ userEntity = null;
+
+ if (!coords.IsValid(EntityManager))
+ {
+ Logger.InfoS("system.interaction", $"Invalid Coordinates: client={session}, coords={coords}");
+ return false;
+ }
+
+ if (uid.IsClientSide())
+ {
+ Logger.WarningS("system.interaction",
+ $"Client sent interaction with client-side entity. Session={session}, Uid={uid}");
+ return false;
+ }
+
+ userEntity = session?.AttachedEntity;
+
+ if (userEntity == null || !userEntity.Value.Valid)
+ {
+ Logger.WarningS("system.interaction",
+ $"Client sent interaction with no attached entity. Session={session}");
+ return false;
+ }
+
+ return true;
+ }
+
#endregion
}
diff --git a/Content.Server/PDA/PDAComponent.cs b/Content.Shared/PDA/PDAComponent.cs
similarity index 90%
rename from Content.Server/PDA/PDAComponent.cs
rename to Content.Shared/PDA/PDAComponent.cs
index f20524b80c..6740d766dd 100644
--- a/Content.Server/PDA/PDAComponent.cs
+++ b/Content.Shared/PDA/PDAComponent.cs
@@ -1,6 +1,4 @@
-using System;
-using System.Collections.Generic;
-using Content.Server.Access.Components;
+using Content.Shared.Access.Components;
using Content.Shared.Containers.ItemSlots;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
@@ -8,7 +6,7 @@ using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.ViewVariables;
-namespace Content.Server.PDA
+namespace Content.Shared.PDA
{
[RegisterComponent]
public class PDAComponent : Component
diff --git a/Content.Shared/PDA/SharedPDASystem.cs b/Content.Shared/PDA/SharedPDASystem.cs
new file mode 100644
index 0000000000..8c9eee5f2f
--- /dev/null
+++ b/Content.Shared/PDA/SharedPDASystem.cs
@@ -0,0 +1,65 @@
+using Content.Shared.Access.Components;
+using Content.Shared.Containers.ItemSlots;
+using Robust.Shared.Containers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+
+namespace Content.Shared.PDA
+{
+ public abstract class SharedPDASystem : EntitySystem
+ {
+ [Dependency] protected readonly ItemSlotsSystem ItemSlotsSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnComponentInit);
+ SubscribeLocalEvent(OnComponentRemove);
+
+ SubscribeLocalEvent(OnItemInserted);
+ SubscribeLocalEvent(OnItemRemoved);
+ }
+
+ protected virtual void OnComponentInit(EntityUid uid, PDAComponent pda, ComponentInit args)
+ {
+ if (pda.IdCard != null)
+ pda.IdSlot.StartingItem = pda.IdCard;
+
+ ItemSlotsSystem.AddItemSlot(uid, $"{pda.Name}-id", pda.IdSlot);
+ ItemSlotsSystem.AddItemSlot(uid, $"{pda.Name}-pen", pda.PenSlot);
+
+ UpdatePDAAppearance(pda);
+ }
+
+ private void OnComponentRemove(EntityUid uid, PDAComponent pda, ComponentRemove args)
+ {
+ ItemSlotsSystem.RemoveItemSlot(uid, pda.IdSlot);
+ ItemSlotsSystem.RemoveItemSlot(uid, pda.PenSlot);
+ }
+
+ protected virtual void OnItemInserted(EntityUid uid, PDAComponent pda, EntInsertedIntoContainerMessage args)
+ {
+ if (!pda.Initialized) return;
+
+ if (args.Container.ID == pda.IdSlot.ID)
+ pda.ContainedID = CompOrNull(args.Entity);
+
+ UpdatePDAAppearance(pda);
+ }
+
+ protected virtual void OnItemRemoved(EntityUid uid, PDAComponent pda, EntRemovedFromContainerMessage args)
+ {
+ if (args.Container.ID == pda.IdSlot.ID)
+ pda.ContainedID = null;
+
+ UpdatePDAAppearance(pda);
+ }
+
+ private void UpdatePDAAppearance(PDAComponent pda)
+ {
+ if (TryComp(pda.Owner, out AppearanceComponent ? appearance))
+ appearance.SetData(PDAVisuals.IDCardInserted, pda.ContainedID != null);
+ }
+ }
+}
diff --git a/Content.Shared/Verbs/SharedVerbSystem.cs b/Content.Shared/Verbs/SharedVerbSystem.cs
index d8b1fccae2..64c1a52afc 100644
--- a/Content.Shared/Verbs/SharedVerbSystem.cs
+++ b/Content.Shared/Verbs/SharedVerbSystem.cs
@@ -1,7 +1,5 @@
using System.Collections.Generic;
using Content.Shared.ActionBlocker;
-using Content.Shared.Administration.Logs;
-using Content.Shared.Database;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Robust.Shared.Containers;
@@ -12,10 +10,35 @@ namespace Content.Shared.Verbs
{
public abstract class SharedVerbSystem : EntitySystem
{
- [Dependency] private readonly SharedAdminLogSystem _logSystem = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeAllEvent(HandleExecuteVerb);
+ }
+
+ private void HandleExecuteVerb(ExecuteVerbEvent args, EntitySessionEventArgs eventArgs)
+ {
+ var user = eventArgs.SenderSession.AttachedEntity;
+ if (user == null)
+ return;
+
+ // Get the list of verbs. This effectively also checks that the requested verb is in fact a valid verb that
+ // the user can perform.
+ var verbs = GetLocalVerbs(args.Target, user.Value, args.Type)[args.Type];
+
+ // Note that GetLocalVerbs might waste time checking & preparing unrelated verbs even though we know
+ // precisely which one we want to run. However, MOST entities will only have 1 or 2 verbs of a given type.
+ // The one exception here is the "other" verb type, which has 3-4 verbs + all the debug verbs.
+
+ // Find the requested verb.
+ if (verbs.TryGetValue(args.RequestedVerb, out var verb))
+ ExecuteVerb(verb, user.Value, args.Target);
+ }
+
///
/// Raises a number of events in order to get all verbs of the given type(s) defined in local systems. This
/// does not request verbs from the server.
@@ -92,60 +115,6 @@ namespace Content.Shared.Verbs
///
/// This will try to call the action delegates and raise the local events for the given verb.
///
- public void ExecuteVerb(Verb verb, EntityUid user, EntityUid target, bool forced = false)
- {
- // first, lets log the verb. Just in case it ends up crashing the server or something.
- LogVerb(verb, user, target, forced);
-
- // then invoke any relevant actions
- verb.Act?.Invoke();
-
- // Maybe raise a local event
- if (verb.ExecutionEventArgs != null)
- {
- if (verb.EventTarget.IsValid())
- RaiseLocalEvent(verb.EventTarget, verb.ExecutionEventArgs);
- else
- RaiseLocalEvent(verb.ExecutionEventArgs);
- }
- }
-
- public void LogVerb(Verb verb, EntityUid user, EntityUid target, bool forced)
- {
- // first get the held item. again.
- EntityUid usedUid = default;
- if (EntityManager.TryGetComponent(user, out SharedHandsComponent? hands) &&
- hands.TryGetActiveHeldEntity(out var heldEntity))
- {
- usedUid = heldEntity;
- if (usedUid != default && EntityManager.TryGetComponent(usedUid, out HandVirtualItemComponent? pull))
- usedUid = pull.BlockingEntity;
- }
-
- // get all the entities
- if (!user.IsValid() || !target.IsValid())
- return;
-
- EntityUid? used = null;
- if (usedUid != default)
- EntityManager.EntityExists(usedUid);
-
- var verbText = $"{verb.Category?.Text} {verb.Text}".Trim();
-
- // lets not frame people, eh?
- var executionText = forced ? "was forced to execute" : "executed";
-
- if (used == null)
- {
- _logSystem.Add(LogType.Verb, verb.Impact,
- $"{ToPrettyString(user):user} {executionText} the [{verbText:verb}] verb targeting {ToPrettyString(target):target}");
- }
- else
- {
- _logSystem.Add(LogType.Verb, verb.Impact,
- $"{ToPrettyString(user):user} {executionText} the [{verbText:verb}] verb targeting {ToPrettyString(target):target} while holding {ToPrettyString(used.Value):held}");
- }
-
- }
+ public abstract void ExecuteVerb(Verb verb, EntityUid user, EntityUid target, bool forced = false);
}
}
diff --git a/Content.Shared/Weapons/Melee/AttackEvent.cs b/Content.Shared/Weapons/Melee/AttackEvent.cs
index bf723ba586..2772aeb239 100644
--- a/Content.Shared/Weapons/Melee/AttackEvent.cs
+++ b/Content.Shared/Weapons/Melee/AttackEvent.cs
@@ -25,7 +25,7 @@ namespace Content.Shared.Weapons.Melee
public EntityCoordinates ClickLocation { get; }
///
- /// UID of the entity that was attacked.
+ /// The entity that was attacked.
///
public EntityUid? Target { get; }