Verb predict (#5638)
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
using Content.Shared.Access;
|
using Content.Shared.Access.Systems;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace Content.Client.Access
|
namespace Content.Client.Access
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Shared.Access;
|
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using static Content.Shared.Access.SharedIdCardConsoleComponent;
|
using static Content.Shared.Access.Components.SharedIdCardConsoleComponent;
|
||||||
|
|
||||||
namespace Content.Client.Access.UI
|
namespace Content.Client.Access.UI
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using Robust.Client.UserInterface.XAML;
|
|||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using static Content.Shared.Access.SharedIdCardConsoleComponent;
|
using static Content.Shared.Access.Components.SharedIdCardConsoleComponent;
|
||||||
|
|
||||||
namespace Content.Client.Access.UI
|
namespace Content.Client.Access.UI
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ namespace Content.Client.Entry
|
|||||||
"ResearchServer",
|
"ResearchServer",
|
||||||
"ResearchPointSource",
|
"ResearchPointSource",
|
||||||
"ResearchClient",
|
"ResearchClient",
|
||||||
"IdCard",
|
|
||||||
"Access",
|
"Access",
|
||||||
"AccessReader",
|
"AccessReader",
|
||||||
"IdCardConsole",
|
"IdCardConsole",
|
||||||
@@ -155,7 +154,6 @@ namespace Content.Client.Entry
|
|||||||
"RefillableSolution",
|
"RefillableSolution",
|
||||||
"DrainableSolution",
|
"DrainableSolution",
|
||||||
"ExaminableSolution",
|
"ExaminableSolution",
|
||||||
"FitsInDispenser",
|
|
||||||
"DrawableSolution",
|
"DrawableSolution",
|
||||||
"InjectableSolution",
|
"InjectableSolution",
|
||||||
"Barotrauma",
|
"Barotrauma",
|
||||||
@@ -295,7 +293,6 @@ namespace Content.Client.Entry
|
|||||||
"BatteryCharger",
|
"BatteryCharger",
|
||||||
"UnpoweredFlashlight",
|
"UnpoweredFlashlight",
|
||||||
"Uplink",
|
"Uplink",
|
||||||
"PDA",
|
|
||||||
"SpawnItemsOnUse",
|
"SpawnItemsOnUse",
|
||||||
"AmbientOnPowered",
|
"AmbientOnPowered",
|
||||||
"Wieldable",
|
"Wieldable",
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ namespace Content.Client.Items.Managers
|
|||||||
}
|
}
|
||||||
else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld)
|
else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld)
|
||||||
{
|
{
|
||||||
_entityManager.EntityNetManager?.SendSystemNetworkMessage(new InteractInventorySlotEvent(item, altInteract: true));
|
_entityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(item, altInteract: true));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
10
Content.Client/PDA/PDASystem.cs
Normal file
10
Content.Client/PDA/PDASystem.cs
Normal file
@@ -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.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -192,27 +192,43 @@ namespace Content.Client.Verbs
|
|||||||
/// Execute actions associated with the given verb.
|
/// Execute actions associated with the given verb.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Unless this is a client-exclusive verb, this will also tell the server to run the same verb. However, if the verb
|
/// Unless this is a client-exclusive verb, this will also tell the server to run the same verb.
|
||||||
/// is disabled and has a tooltip, this function will only generate a pop-up-message instead of executing anything.
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public void ExecuteVerb(EntityUid target, Verb verb, VerbType verbType)
|
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;
|
var user = _playerManager.LocalPlayer?.ControlledEntity;
|
||||||
if (user == null)
|
if (user == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ExecuteVerb(verb, user.Value, target);
|
// is this verb actually valid?
|
||||||
|
if (verb.Disabled)
|
||||||
if (!verb.ClientExclusive)
|
|
||||||
{
|
{
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Access.Components;
|
|
||||||
using Content.Server.Hands.Components;
|
using Content.Server.Hands.Components;
|
||||||
using Content.Server.Inventory.Components;
|
using Content.Server.Inventory.Components;
|
||||||
using Content.Server.Items;
|
using Content.Server.Items;
|
||||||
using Content.Server.PDA;
|
using Content.Server.PDA;
|
||||||
|
using Content.Shared.Access.Components;
|
||||||
using Content.Shared.Containers.ItemSlots;
|
using Content.Shared.Containers.ItemSlots;
|
||||||
|
using Content.Shared.PDA;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -78,7 +79,7 @@ namespace Content.IntegrationTests.Tests.PDA
|
|||||||
|
|
||||||
var itemSlots = sEntityManager.GetComponent<ItemSlotsComponent>(dummyPda);
|
var itemSlots = sEntityManager.GetComponent<ItemSlotsComponent>(dummyPda);
|
||||||
sEntityManager.EntitySysManager.GetEntitySystem<ItemSlotsSystem>()
|
sEntityManager.EntitySysManager.GetEntitySystem<ItemSlotsSystem>()
|
||||||
.TryInsert(dummyPda, pdaComponent.IdSlot, pdaIdCard);
|
.TryInsert(dummyPda, pdaComponent.IdSlot, pdaIdCard, null);
|
||||||
var pdaContainedId = pdaComponent.ContainedID;
|
var pdaContainedId = pdaComponent.ContainedID;
|
||||||
|
|
||||||
// The PDA in the hand should be found first
|
// The PDA in the hand should be found first
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Content.Server.Access.Systems;
|
|||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
using Content.Server.UserInterface;
|
using Content.Server.UserInterface;
|
||||||
using Content.Shared.Access;
|
using Content.Shared.Access;
|
||||||
|
using Content.Shared.Access.Components;
|
||||||
using Content.Shared.Containers.ItemSlots;
|
using Content.Shared.Containers.ItemSlots;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -115,6 +116,9 @@ namespace Content.Server.Access.Components
|
|||||||
|
|
||||||
public void UpdateUserInterface()
|
public void UpdateUserInterface()
|
||||||
{
|
{
|
||||||
|
if (!Initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
IdCardConsoleBoundUserInterfaceState newState;
|
IdCardConsoleBoundUserInterfaceState newState;
|
||||||
// this could be prettier
|
// this could be prettier
|
||||||
if (TargetIdSlot.Item is not {Valid: true} targetIdEntity)
|
if (TargetIdSlot.Item is not {Valid: true} targetIdEntity)
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ using System.Linq;
|
|||||||
using Content.Server.Access.Components;
|
using Content.Server.Access.Components;
|
||||||
using Content.Server.Inventory.Components;
|
using Content.Server.Inventory.Components;
|
||||||
using Content.Server.Items;
|
using Content.Server.Items;
|
||||||
using Content.Server.PDA;
|
|
||||||
using Content.Shared.Access;
|
using Content.Shared.Access;
|
||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
|
using Content.Shared.PDA;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Log;
|
using Robust.Shared.Log;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Content.Server.Access.Components;
|
using Content.Server.Access.Components;
|
||||||
using Content.Shared.Access;
|
using Content.Shared.Access.Systems;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
using Content.Server.Access.Components;
|
|
||||||
using Content.Server.Inventory.Components;
|
using Content.Server.Inventory.Components;
|
||||||
using Content.Server.Items;
|
using Content.Server.Items;
|
||||||
using Content.Server.PDA;
|
|
||||||
using Content.Shared.Access;
|
|
||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
|
using Content.Shared.PDA;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Robust.Shared.IoC;
|
using Content.Shared.Access.Components;
|
||||||
|
using Content.Shared.Access.Systems;
|
||||||
|
|
||||||
namespace Content.Server.Access.Systems
|
namespace Content.Server.Access.Systems
|
||||||
{
|
{
|
||||||
public class IdCardSystem : EntitySystem
|
public class IdCardSystem : SharedIdCardSystem
|
||||||
{
|
{
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ using Content.Server.Administration.UI;
|
|||||||
using Content.Server.EUI;
|
using Content.Server.EUI;
|
||||||
using Content.Server.Inventory.Components;
|
using Content.Server.Inventory.Components;
|
||||||
using Content.Server.Items;
|
using Content.Server.Items;
|
||||||
using Content.Server.PDA;
|
|
||||||
using Content.Server.Preferences.Managers;
|
using Content.Server.Preferences.Managers;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
|
using Content.Shared.PDA;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ namespace Content.Server.Cabinet
|
|||||||
|
|
||||||
private void OnContainerModified(EntityUid uid, ItemCabinetComponent cabinet, ContainerModifiedMessage args)
|
private void OnContainerModified(EntityUid uid, ItemCabinetComponent cabinet, ContainerModifiedMessage args)
|
||||||
{
|
{
|
||||||
|
if (!cabinet.Initialized) return;
|
||||||
|
|
||||||
if (args.Container.ID == cabinet.CabinetSlot.ID)
|
if (args.Container.ID == cabinet.CabinetSlot.ID)
|
||||||
UpdateAppearance(uid, cabinet);
|
UpdateAppearance(uid, cabinet);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ namespace Content.Server.Cargo.Components
|
|||||||
// attempt to attach the label
|
// attempt to attach the label
|
||||||
if (_entMan.TryGetComponent(product, out PaperLabelComponent label))
|
if (_entMan.TryGetComponent(product, out PaperLabelComponent label))
|
||||||
{
|
{
|
||||||
EntitySystem.Get<ItemSlotsSystem>().TryInsert(Owner, label.LabelSlot, printed);
|
EntitySystem.Get<ItemSlotsSystem>().TryInsert(Owner, label.LabelSlot, printed, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -197,6 +197,8 @@ namespace Content.Server.Chemistry.Components
|
|||||||
|
|
||||||
public void UpdateUserInterface()
|
public void UpdateUserInterface()
|
||||||
{
|
{
|
||||||
|
if (!Initialized) return;
|
||||||
|
|
||||||
var state = GetUserInterfaceState();
|
var state = GetUserInterfaceState();
|
||||||
UserInterface?.SetState(state);
|
UserInterface?.SetState(state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,6 +241,8 @@ namespace Content.Server.Chemistry.Components
|
|||||||
|
|
||||||
public void UpdateUserInterface()
|
public void UpdateUserInterface()
|
||||||
{
|
{
|
||||||
|
if (!Initialized) return;
|
||||||
|
|
||||||
var state = GetUserInterfaceState();
|
var state = GetUserInterfaceState();
|
||||||
UserInterface?.SetState(state);
|
UserInterface?.SetState(state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.Chemistry.Components.SolutionManager;
|
using Content.Server.Chemistry.Components.SolutionManager;
|
||||||
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.Chemistry.Components;
|
using Content.Server.Chemistry.Components;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Weapons.Melee;
|
using Content.Shared.Weapons.Melee;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ using Content.Shared.Database;
|
|||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
using Content.Shared.Ghost;
|
using Content.Shared.Ghost;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
|
using Content.Shared.PDA;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Content.Shared.Station;
|
using Content.Shared.Station;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ using Content.Shared.Damage;
|
|||||||
using Content.Shared.Damage.Prototypes;
|
using Content.Shared.Damage.Prototypes;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.MobState.Components;
|
using Content.Shared.MobState.Components;
|
||||||
|
using Content.Shared.PDA;
|
||||||
using Content.Shared.Traitor.Uplink;
|
using Content.Shared.Traitor.Uplink;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
|
|||||||
@@ -41,19 +41,17 @@ namespace Content.Server.Interaction
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||||
[Dependency] private readonly PullingSystem _pullSystem = default!;
|
[Dependency] private readonly PullingSystem _pullSystem = default!;
|
||||||
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
|
|
||||||
[Dependency] private readonly AdminLogSystem _adminLogSystem = default!;
|
[Dependency] private readonly AdminLogSystem _adminLogSystem = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeNetworkEvent<DragDropRequestEvent>(HandleDragDropRequestEvent);
|
SubscribeNetworkEvent<DragDropRequestEvent>(HandleDragDropRequestEvent);
|
||||||
SubscribeNetworkEvent<InteractInventorySlotEvent>(HandleInteractInventorySlotEvent);
|
|
||||||
|
|
||||||
CommandBinds.Builder
|
CommandBinds.Builder
|
||||||
.Bind(EngineKeyFunctions.Use,
|
.Bind(EngineKeyFunctions.Use,
|
||||||
new PointerInputCmdHandler(HandleUseInteraction))
|
new PointerInputCmdHandler(HandleUseInteraction))
|
||||||
.Bind(ContentKeyFunctions.AltActivateItemInWorld,
|
|
||||||
new PointerInputCmdHandler(HandleAltUseInteraction))
|
|
||||||
.Bind(ContentKeyFunctions.WideAttack,
|
.Bind(ContentKeyFunctions.WideAttack,
|
||||||
new PointerInputCmdHandler(HandleWideAttack))
|
new PointerInputCmdHandler(HandleWideAttack))
|
||||||
.Bind(ContentKeyFunctions.ActivateItemInWorld,
|
.Bind(ContentKeyFunctions.ActivateItemInWorld,
|
||||||
@@ -69,36 +67,6 @@ namespace Content.Server.Interaction
|
|||||||
base.Shutdown();
|
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)
|
public override bool CanAccessViaStorage(EntityUid user, EntityUid target)
|
||||||
{
|
{
|
||||||
if (Deleted(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.
|
// 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);
|
return storage.SubscribedSessions.Contains(actor.PlayerSession);
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
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
|
#region Drag drop
|
||||||
private void HandleDragDropRequestEvent(DragDropRequestEvent msg, EntitySessionEventArgs args)
|
private void HandleDragDropRequestEvent(DragDropRequestEvent msg, EntitySessionEventArgs args)
|
||||||
@@ -258,20 +195,6 @@ namespace Content.Server.Interaction
|
|||||||
return true;
|
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)
|
private bool HandleTryPullObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||||
{
|
{
|
||||||
if (!ValidateClientInput(session, coords, uid, out var userEntity))
|
if (!ValidateClientInput(session, coords, uid, out var userEntity))
|
||||||
@@ -295,97 +218,14 @@ namespace Content.Server.Interaction
|
|||||||
return _pullSystem.TogglePull(userEntity.Value, pull);
|
return _pullSystem.TogglePull(userEntity.Value, pull);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resolves user interactions with objects.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Checks Whether combat mode is enabled and whether the user can actually interact with the given entity.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="altInteract">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.</param>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Uses an empty hand on an entity
|
/// Uses an empty hand on an entity
|
||||||
/// Finds components with the InteractHand interface and calls their function
|
/// Finds components with the InteractHand interface and calls their function
|
||||||
/// NOTE: Does not have an InRangeUnobstructed check
|
/// NOTE: Does not have an InRangeUnobstructed check
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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))
|
if (!_actionBlockerSystem.CanInteract(user))
|
||||||
return;
|
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
|
/// 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
|
/// Or it will use the used entity itself on the position clicked, regardless of what was there
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<bool> InteractUsingRanged(EntityUid user, EntityUid used, EntityUid? target, EntityCoordinates clickLocation, bool inRangeUnobstructed)
|
public override async Task<bool> 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))
|
if (InteractDoBefore(user, used, inRangeUnobstructed ? target : null, clickLocation, false))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -445,8 +286,9 @@ namespace Content.Server.Interaction
|
|||||||
return await InteractDoAfter(user, used, inRangeUnobstructed ? target : null, clickLocation, false);
|
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))
|
if (!ValidateInteractAndFace(user, coordinates))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -456,10 +298,10 @@ namespace Content.Server.Interaction
|
|||||||
if (!wideAttack)
|
if (!wideAttack)
|
||||||
{
|
{
|
||||||
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
|
// 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",
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,15 +330,15 @@ namespace Content.Server.Interaction
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var ev = new ClickAttackEvent(item.Value, user, coordinates, targetUid);
|
var ev = new ClickAttackEvent(item.Value, user, coordinates, target);
|
||||||
RaiseLocalEvent(item.Value, ev, false);
|
RaiseLocalEvent(item.Value, ev, false);
|
||||||
|
|
||||||
if (ev.Handled)
|
if (ev.Handled)
|
||||||
{
|
{
|
||||||
if (targetUid != null)
|
if (target != null)
|
||||||
{
|
{
|
||||||
_adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium,
|
_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
|
else
|
||||||
{
|
{
|
||||||
@@ -508,10 +350,10 @@ namespace Content.Server.Interaction
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!wideAttack && targetUid != null && HasComp<ItemComponent>(targetUid.Value))
|
else if (!wideAttack && target != null && HasComp<ItemComponent>(target.Value))
|
||||||
{
|
{
|
||||||
// We pick up items if our hand is empty, even if we're in combat mode.
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -527,14 +369,14 @@ namespace Content.Server.Interaction
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var ev = new ClickAttackEvent(user, user, coordinates, targetUid);
|
var ev = new ClickAttackEvent(user, user, coordinates, target);
|
||||||
RaiseLocalEvent(user, ev, false);
|
RaiseLocalEvent(user, ev, false);
|
||||||
if (ev.Handled)
|
if (ev.Handled)
|
||||||
{
|
{
|
||||||
if (targetUid != null)
|
if (target != null)
|
||||||
{
|
{
|
||||||
_adminLogSystem.Add(LogType.AttackUnarmedClick, LogImpact.Medium,
|
_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
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -85,9 +85,10 @@ namespace Content.Server.Labels
|
|||||||
args.PushMarkup(text.TrimEnd());
|
args.PushMarkup(text.TrimEnd());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void OnContainerModified(EntityUid uid, PaperLabelComponent label, ContainerModifiedMessage args)
|
private void OnContainerModified(EntityUid uid, PaperLabelComponent label, ContainerModifiedMessage args)
|
||||||
{
|
{
|
||||||
|
if (!label.Initialized) return;
|
||||||
|
|
||||||
if (args.Container.ID != label.LabelSlot.ID)
|
if (args.Container.ID != label.LabelSlot.ID)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ namespace Content.Server.Nuke
|
|||||||
{
|
{
|
||||||
component.RemainingTime = component.Timer;
|
component.RemainingTime = component.Timer;
|
||||||
_itemSlots.AddItemSlot(uid, component.Name, component.DiskSlot);
|
_itemSlots.AddItemSlot(uid, component.Name, component.DiskSlot);
|
||||||
|
|
||||||
|
UpdateStatus(uid, component);
|
||||||
|
UpdateUserInterface(uid, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
@@ -101,6 +104,8 @@ namespace Content.Server.Nuke
|
|||||||
|
|
||||||
private void OnItemSlotChanged(EntityUid uid, NukeComponent component, ContainerModifiedMessage args)
|
private void OnItemSlotChanged(EntityUid uid, NukeComponent component, ContainerModifiedMessage args)
|
||||||
{
|
{
|
||||||
|
if (!component.Initialized) return;
|
||||||
|
|
||||||
if (args.Container.ID != component.DiskSlot.ID)
|
if (args.Container.ID != component.DiskSlot.ID)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Content.Server.Access.Components;
|
|
||||||
using Content.Server.Hands.Components;
|
using Content.Server.Hands.Components;
|
||||||
using Content.Server.Inventory.Components;
|
using Content.Server.Inventory.Components;
|
||||||
|
using Content.Shared.Access.Components;
|
||||||
|
using Content.Shared.PDA;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using Content.Server.Access.Components;
|
|
||||||
using Content.Server.Light.Components;
|
using Content.Server.Light.Components;
|
||||||
using Content.Server.Light.EntitySystems;
|
using Content.Server.Light.EntitySystems;
|
||||||
using Content.Server.Light.Events;
|
using Content.Server.Light.Events;
|
||||||
@@ -15,9 +14,8 @@ using Robust.Shared.IoC;
|
|||||||
|
|
||||||
namespace Content.Server.PDA
|
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 UplinkSystem _uplinkSystem = default!;
|
||||||
[Dependency] private readonly UnpoweredFlashlightSystem _unpoweredFlashlight = default!;
|
[Dependency] private readonly UnpoweredFlashlightSystem _unpoweredFlashlight = default!;
|
||||||
|
|
||||||
@@ -25,36 +23,21 @@ namespace Content.Server.PDA
|
|||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<PDAComponent, ComponentInit>(OnComponentInit);
|
|
||||||
SubscribeLocalEvent<PDAComponent, ComponentRemove>(OnComponentRemove);
|
|
||||||
|
|
||||||
SubscribeLocalEvent<PDAComponent, ActivateInWorldEvent>(OnActivateInWorld);
|
SubscribeLocalEvent<PDAComponent, ActivateInWorldEvent>(OnActivateInWorld);
|
||||||
SubscribeLocalEvent<PDAComponent, UseInHandEvent>(OnUse);
|
SubscribeLocalEvent<PDAComponent, UseInHandEvent>(OnUse);
|
||||||
SubscribeLocalEvent<PDAComponent, EntInsertedIntoContainerMessage>(OnItemInserted);
|
|
||||||
SubscribeLocalEvent<PDAComponent, EntRemovedFromContainerMessage>(OnItemRemoved);
|
|
||||||
SubscribeLocalEvent<PDAComponent, LightToggleEvent>(OnLightToggle);
|
SubscribeLocalEvent<PDAComponent, LightToggleEvent>(OnLightToggle);
|
||||||
|
|
||||||
SubscribeLocalEvent<PDAComponent, UplinkInitEvent>(OnUplinkInit);
|
|
||||||
SubscribeLocalEvent<PDAComponent, UplinkRemovedEvent>(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);
|
var ui = pda.Owner.GetUIOrNull(PDAUiKey.Key);
|
||||||
if (ui != null)
|
if (ui != null)
|
||||||
ui.OnReceiveMessage += (msg) => OnUIMessage(pda, msg);
|
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)
|
private void OnUse(EntityUid uid, PDAComponent pda, UseInHandEvent args)
|
||||||
{
|
{
|
||||||
@@ -70,21 +53,15 @@ namespace Content.Server.PDA
|
|||||||
args.Handled = OpenUI(pda, args.User);
|
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)
|
base.OnItemInserted(uid, pda, args);
|
||||||
pda.ContainedID = EntityManager.GetComponentOrNull<IdCardComponent>(args.Entity);
|
|
||||||
|
|
||||||
UpdatePDAAppearance(pda);
|
|
||||||
UpdatePDAUserInterface(pda);
|
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)
|
base.OnItemRemoved(uid, pda, args);
|
||||||
pda.ContainedID = null;
|
|
||||||
|
|
||||||
UpdatePDAAppearance(pda);
|
|
||||||
UpdatePDAUserInterface(pda);
|
UpdatePDAUserInterface(pda);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,12 +139,12 @@ namespace Content.Server.PDA
|
|||||||
|
|
||||||
case PDAEjectIDMessage _:
|
case PDAEjectIDMessage _:
|
||||||
{
|
{
|
||||||
_itemSlotsSystem.TryEjectToHands(pda.Owner, pda.IdSlot, playerUid);
|
ItemSlotsSystem.TryEjectToHands(pda.Owner, pda.IdSlot, playerUid);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PDAEjectPenMessage _:
|
case PDAEjectPenMessage _:
|
||||||
{
|
{
|
||||||
_itemSlotsSystem.TryEjectToHands(pda.Owner, pda.PenSlot, playerUid);
|
ItemSlotsSystem.TryEjectToHands(pda.Owner, pda.PenSlot, playerUid);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PDAShowUplinkMessage _:
|
case PDAShowUplinkMessage _:
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ using Content.Server.GameTicking;
|
|||||||
using Content.Server.Hands.Components;
|
using Content.Server.Hands.Components;
|
||||||
using Content.Server.Inventory.Components;
|
using Content.Server.Inventory.Components;
|
||||||
using Content.Server.Items;
|
using Content.Server.Items;
|
||||||
using Content.Server.PDA;
|
|
||||||
using Content.Shared.Access;
|
using Content.Shared.Access;
|
||||||
|
using Content.Shared.Access.Components;
|
||||||
using Content.Shared.Containers.ItemSlots;
|
using Content.Shared.Containers.ItemSlots;
|
||||||
|
using Content.Shared.PDA;
|
||||||
using Content.Shared.Sandbox;
|
using Content.Shared.Sandbox;
|
||||||
using Robust.Server.Console;
|
using Robust.Server.Console;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
@@ -139,7 +140,7 @@ namespace Content.Server.Sandbox
|
|||||||
if (_entityManager.TryGetComponent(pda.Owner, out ItemSlotsComponent? itemSlots))
|
if (_entityManager.TryGetComponent(pda.Owner, out ItemSlotsComponent? itemSlots))
|
||||||
{
|
{
|
||||||
_entityManager.EntitySysManager.GetEntitySystem<ItemSlotsSystem>().
|
_entityManager.EntitySysManager.GetEntitySystem<ItemSlotsSystem>().
|
||||||
TryInsert(wornItem.Owner, pda.IdSlot, newID);
|
TryInsert(wornItem.Owner, pda.IdSlot, newID, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ using System.Linq;
|
|||||||
using Content.Server.Hands.Components;
|
using Content.Server.Hands.Components;
|
||||||
using Content.Server.Inventory.Components;
|
using Content.Server.Inventory.Components;
|
||||||
using Content.Server.Items;
|
using Content.Server.Items;
|
||||||
using Content.Server.PDA;
|
|
||||||
using Content.Server.Traitor.Uplink.Account;
|
using Content.Server.Traitor.Uplink.Account;
|
||||||
using Content.Server.Traitor.Uplink.Components;
|
using Content.Server.Traitor.Uplink.Components;
|
||||||
using Content.Server.UserInterface;
|
using Content.Server.UserInterface;
|
||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.PDA;
|
||||||
using Content.Shared.Traitor.Uplink;
|
using Content.Shared.Traitor.Uplink;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
|
|||||||
@@ -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 Content.Shared.Verbs;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Log;
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
|
||||||
namespace Content.Server.Verbs
|
namespace Content.Server.Verbs
|
||||||
{
|
{
|
||||||
public sealed class VerbSystem : SharedVerbSystem
|
public sealed class VerbSystem : SharedVerbSystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly SharedAdminLogSystem _logSystem = default!;
|
||||||
|
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeNetworkEvent<RequestServerVerbsEvent>(HandleVerbRequest);
|
SubscribeNetworkEvent<RequestServerVerbsEvent>(HandleVerbRequest);
|
||||||
SubscribeNetworkEvent<ExecuteVerbEvent>(HandleTryExecuteVerb);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when asked over the network to run a given verb.
|
|
||||||
/// </summary>
|
|
||||||
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)
|
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));
|
var response = new VerbsResponseEvent(args.EntityUid, GetLocalVerbs(args.EntityUid, attached, args.Type));
|
||||||
RaiseNetworkEvent(response, player.ConnectedClient);
|
RaiseNetworkEvent(response, player.ConnectedClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Execute the provided verb.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This will try to call the action delegates and raise the local events for the given verb.
|
||||||
|
/// </remarks>
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
using Content.Server.Access.Systems;
|
using Content.Shared.Access.Systems;
|
||||||
using Content.Server.PDA;
|
using Content.Shared.PDA;
|
||||||
using Robust.Shared.Analyzers;
|
using Robust.Shared.Analyzers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
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]
|
[RegisterComponent]
|
||||||
[Friend(typeof(IdCardSystem), typeof(PDASystem))]
|
[Friend(typeof(SharedIdCardSystem), typeof(SharedPDASystem))]
|
||||||
public class IdCardComponent : Component
|
public class IdCardComponent : Component
|
||||||
{
|
{
|
||||||
public override string Name => "IdCard";
|
public override string Name => "IdCard";
|
||||||
@@ -5,7 +5,7 @@ using Robust.Shared.GameObjects;
|
|||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
namespace Content.Shared.Access
|
namespace Content.Shared.Access.Components
|
||||||
{
|
{
|
||||||
public class SharedIdCardConsoleComponent : Component
|
public class SharedIdCardConsoleComponent : Component
|
||||||
{
|
{
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
|
using Content.Shared.Access.Components;
|
||||||
using Content.Shared.Containers.ItemSlots;
|
using Content.Shared.Containers.ItemSlots;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
namespace Content.Shared.Access
|
namespace Content.Shared.Access.Systems
|
||||||
{
|
{
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public abstract class SharedIdCardConsoleSystem : EntitySystem
|
public abstract class SharedIdCardConsoleSystem : EntitySystem
|
||||||
8
Content.Shared/Access/Systems/SharedIdCardSystem.cs
Normal file
8
Content.Shared/Access/Systems/SharedIdCardSystem.cs
Normal file
@@ -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?
|
||||||
|
}
|
||||||
@@ -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.Serialization.Manager.Attributes;
|
||||||
using Robust.Shared.ViewVariables;
|
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 <c>SharedReagentDispenserComponent</c>.
|
/// Allows the entity with this component to be placed in a <c>SharedReagentDispenserComponent</c>.
|
||||||
/// <para>Otherwise it's considered to be too large or the improper shape to fit.</para>
|
/// <para>Otherwise it's considered to be too large or the improper shape to fit.</para>
|
||||||
/// <para>Allows us to have obscenely large containers that are harder to abuse in chem dispensers
|
/// <para>Allows us to have obscenely large containers that are harder to abuse in chem dispensers
|
||||||
/// since they can't be placed directly in them.</para>
|
/// since they can't be placed directly in them.</para>
|
||||||
/// <see cref="Content.Shared.Chemistry.Dispenser.SharedReagentDispenserComponent"/>
|
/// <see cref="Dispenser.SharedReagentDispenserComponent"/>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
|
[NetworkedComponent] // only needed for white-lists. Client doesn't actually need Solution data;
|
||||||
public class FitsInDispenserComponent : Component
|
public class FitsInDispenserComponent : Component
|
||||||
{
|
{
|
||||||
public override string Name => "FitsInDispenser";
|
public override string Name => "FitsInDispenser";
|
||||||
@@ -3,6 +3,7 @@ using Content.Shared.Acts;
|
|||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Sound;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
@@ -12,6 +13,7 @@ using Robust.Shared.IoC;
|
|||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Log;
|
using Robust.Shared.Log;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
@@ -24,6 +26,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -174,7 +177,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
if (slot.Item != null)
|
if (slot.Item != null)
|
||||||
hands.TryPutInAnyHand(slot.Item.Value);
|
hands.TryPutInAnyHand(slot.Item.Value);
|
||||||
|
|
||||||
Insert(uid, slot, args.Used);
|
Insert(uid, slot, args.Used, args.User, true);
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
return;
|
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 <see
|
/// Insert an item into a slot. This does not perform checks, so make sure to also use <see
|
||||||
/// cref="CanInsert"/> or just use <see cref="TryInsert"/> instead.
|
/// cref="CanInsert"/> or just use <see cref="TryInsert"/> instead.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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);
|
slot.ContainerSlot.Insert(item);
|
||||||
// ContainerSlot automatically raises a directed EntInsertedIntoContainerMessage
|
// ContainerSlot automatically raises a directed EntInsertedIntoContainerMessage
|
||||||
|
|
||||||
if (slot.InsertSound != null)
|
PlaySound(uid, slot.InsertSound, slot.SoundOptions, userAudio ? null : user);
|
||||||
SoundSystem.Play(Filter.Pvs(uid), slot.InsertSound.GetSound(), uid, slot.SoundOptions);
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plays a sound
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid">Source of the sound</param>
|
||||||
|
/// <param name="sound">The sound</param>
|
||||||
|
/// <param name="excluded">Optional (server-side) argument used to prevent sending the audio to a specific
|
||||||
|
/// user.</param>
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -228,7 +250,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
/// Tries to insert item into a specific slot.
|
/// Tries to insert item into a specific slot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>False if failed to insert item</returns>
|
/// <returns>False if failed to insert item</returns>
|
||||||
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))
|
if (!Resolve(uid, ref itemSlots))
|
||||||
return false;
|
return false;
|
||||||
@@ -236,19 +258,19 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
if (!itemSlots.Slots.TryGetValue(id, out var slot))
|
if (!itemSlots.Slots.TryGetValue(id, out var slot))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return TryInsert(uid, slot, item);
|
return TryInsert(uid, slot, item, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to insert item into a specific slot.
|
/// Tries to insert item into a specific slot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>False if failed to insert item</returns>
|
/// <returns>False if failed to insert item</returns>
|
||||||
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))
|
if (!CanInsert(uid, item, slot))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Insert(uid, slot, item);
|
Insert(uid, slot, item, user);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +293,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
if (!_actionBlockerSystem.CanInteract(user) && hands.Drop(item))
|
if (!_actionBlockerSystem.CanInteract(user) && hands.Drop(item))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Insert(uid, slot, item);
|
Insert(uid, slot, item, user);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endregion
|
#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
|
/// Eject an item into a slot. This does not perform checks (e.g., is the slot locked?), so you should
|
||||||
/// probably just use <see cref="TryEject"/> instead.
|
/// probably just use <see cref="TryEject"/> instead.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void Eject(EntityUid uid, ItemSlot slot, EntityUid item)
|
private void Eject(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user)
|
||||||
{
|
{
|
||||||
slot.ContainerSlot.Remove(item);
|
slot.ContainerSlot.Remove(item);
|
||||||
// ContainerSlot automatically raises a directed EntRemovedFromContainerMessage
|
// ContainerSlot automatically raises a directed EntRemovedFromContainerMessage
|
||||||
|
|
||||||
if (slot.EjectSound != null)
|
PlaySound(uid, slot.EjectSound, slot.SoundOptions, user);
|
||||||
SoundSystem.Play(Filter.Pvs(uid), slot.EjectSound.GetSound(), uid, slot.SoundOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Try to eject an item from a slot.
|
/// Try to eject an item from a slot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>False if item slot is locked or has no item inserted</returns>
|
/// <returns>False if item slot is locked or has no item inserted</returns>
|
||||||
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;
|
item = null;
|
||||||
|
|
||||||
@@ -302,7 +323,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
item = slot.Item;
|
item = slot.Item;
|
||||||
Eject(uid, slot, item.Value);
|
Eject(uid, slot, item.Value, user);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +331,8 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
/// Try to eject item from a slot.
|
/// Try to eject item from a slot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>False if the id is not valid, the item slot is locked, or it has no item inserted</returns>
|
/// <returns>False if the id is not valid, the item slot is locked, or it has no item inserted</returns>
|
||||||
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;
|
item = null;
|
||||||
|
|
||||||
@@ -320,7 +342,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
if (!itemSlots.Slots.TryGetValue(id, out var slot))
|
if (!itemSlots.Slots.TryGetValue(id, out var slot))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return TryEject(uid, slot, out item);
|
return TryEject(uid, slot, user, out item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -333,7 +355,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
/// </returns>
|
/// </returns>
|
||||||
public bool TryEjectToHands(EntityUid uid, ItemSlot slot, EntityUid? user)
|
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;
|
return false;
|
||||||
|
|
||||||
if (user != null && EntityManager.TryGetComponent(user.Value, out SharedHandsComponent? hands))
|
if (user != null && EntityManager.TryGetComponent(user.Value, out SharedHandsComponent? hands))
|
||||||
@@ -428,7 +450,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
: EntityManager.GetComponent<MetaDataComponent>(args.Using.Value).EntityName ?? string.Empty;
|
: EntityManager.GetComponent<MetaDataComponent>(args.Using.Value).EntityName ?? string.Empty;
|
||||||
|
|
||||||
Verb insertVerb = new();
|
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)
|
if (slot.InsertVerbText != null)
|
||||||
{
|
{
|
||||||
@@ -461,7 +483,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
foreach (var slot in component.Slots.Values)
|
foreach (var slot in component.Slots.Values)
|
||||||
{
|
{
|
||||||
if (slot.EjectOnBreak && slot.HasItem)
|
if (slot.EjectOnBreak && slot.HasItem)
|
||||||
TryEject(uid, slot, out var _);
|
TryEject(uid, slot, null, out var _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
|
using Content.Shared.CombatMode;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Hands;
|
using Content.Shared.Hands;
|
||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
|
using Content.Shared.Input;
|
||||||
|
using Content.Shared.Interaction.Helpers;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.Physics;
|
using Content.Shared.Physics;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
@@ -16,13 +20,14 @@ using Content.Shared.Verbs;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Input.Binding;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Log;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
using Robust.Shared.Players;
|
using Robust.Shared.Players;
|
||||||
using Robust.Shared.Random;
|
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
#pragma warning disable 618
|
#pragma warning disable 618
|
||||||
@@ -39,12 +44,166 @@ namespace Content.Shared.Interaction
|
|||||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||||
[Dependency] private readonly SharedVerbSystem _verbSystem = default!;
|
[Dependency] private readonly SharedVerbSystem _verbSystem = default!;
|
||||||
[Dependency] private readonly SharedAdminLogSystem _adminLogSystem = default!;
|
[Dependency] private readonly SharedAdminLogSystem _adminLogSystem = default!;
|
||||||
|
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
|
||||||
|
|
||||||
public const float InteractionRange = 2;
|
public const float InteractionRange = 2;
|
||||||
public const float InteractionRangeSquared = InteractionRange * InteractionRange;
|
public const float InteractionRangeSquared = InteractionRange * InteractionRange;
|
||||||
|
|
||||||
public delegate bool Ignored(EntityUid entity);
|
public delegate bool Ignored(EntityUid entity);
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeAllEvent<InteractInventorySlotEvent>(HandleInteractInventorySlotEvent);
|
||||||
|
|
||||||
|
CommandBinds.Builder
|
||||||
|
.Bind(ContentKeyFunctions.AltActivateItemInWorld,
|
||||||
|
new PointerInputCmdHandler(HandleAltUseInteraction))
|
||||||
|
.Register<SharedInteractionSystem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
CommandBinds.Unregister<SharedInteractionSystem>();
|
||||||
|
base.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves user interactions with objects.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Checks Whether combat mode is enabled and whether the user can actually interact with the given entity.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="altInteract">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.</param>
|
||||||
|
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<bool> 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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Traces a ray from coords to otherCoords and returns the length
|
/// Traces a ray from coords to otherCoords and returns the length
|
||||||
/// of the vector between coords and the ray's first hit.
|
/// of the vector between coords and the ray's first hit.
|
||||||
@@ -154,7 +313,7 @@ namespace Content.Shared.Interaction
|
|||||||
|
|
||||||
foreach (var result in rayResults)
|
foreach (var result in rayResults)
|
||||||
{
|
{
|
||||||
if (!EntityManager.TryGetComponent(result.HitEntity, out IPhysBody? p))
|
if (!TryComp(result.HitEntity, out IPhysBody? p))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -213,7 +372,7 @@ namespace Content.Shared.Interaction
|
|||||||
bool popup = false)
|
bool popup = false)
|
||||||
{
|
{
|
||||||
predicate ??= e => e == origin || e == other;
|
predicate ??= e => e == origin || e == other;
|
||||||
return InRangeUnobstructed(origin, EntityManager.GetComponent<TransformComponent>(other).MapPosition, range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
return InRangeUnobstructed(origin, Transform(other).MapPosition, range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -345,7 +504,7 @@ namespace Content.Shared.Interaction
|
|||||||
bool ignoreInsideBlocker = false,
|
bool ignoreInsideBlocker = false,
|
||||||
bool popup = false)
|
bool popup = false)
|
||||||
{
|
{
|
||||||
var originPosition = EntityManager.GetComponent<TransformComponent>(origin).MapPosition;
|
var originPosition = Transform(origin).MapPosition;
|
||||||
predicate ??= e => e == origin;
|
predicate ??= e => e == origin;
|
||||||
|
|
||||||
var inRange = InRangeUnobstructed(originPosition, other, range, collisionMask, predicate, ignoreInsideBlocker);
|
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 interactUsingEventArgs = new InteractUsingEventArgs(user, clickLocation, used, target);
|
||||||
|
|
||||||
var interactUsings = EntityManager.GetComponents<IInteractUsing>(target).OrderByDescending(x => x.Priority);
|
var interactUsings = AllComps<IInteractUsing>(target).OrderByDescending(x => x.Priority);
|
||||||
foreach (var interactUsing in interactUsings)
|
foreach (var interactUsing in interactUsings)
|
||||||
{
|
{
|
||||||
// If an InteractUsing returns a status completion we finish our interaction
|
// If an InteractUsing returns a status completion we finish our interaction
|
||||||
@@ -418,7 +577,7 @@ namespace Content.Shared.Interaction
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
var afterInteractEventArgs = new AfterInteractEventArgs(user, clickLocation, target, canReach);
|
var afterInteractEventArgs = new AfterInteractEventArgs(user, clickLocation, target, canReach);
|
||||||
var afterInteracts = EntityManager.GetComponents<IAfterInteract>(used).OrderByDescending(x => x.Priority).ToList();
|
var afterInteracts = AllComps<IAfterInteract>(used).OrderByDescending(x => x.Priority).ToList();
|
||||||
|
|
||||||
foreach (var afterInteract in afterInteracts)
|
foreach (var afterInteract in afterInteracts)
|
||||||
{
|
{
|
||||||
@@ -444,7 +603,7 @@ namespace Content.Shared.Interaction
|
|||||||
|
|
||||||
protected void InteractionActivate(EntityUid user, EntityUid used)
|
protected void InteractionActivate(EntityUid user, EntityUid used)
|
||||||
{
|
{
|
||||||
if (EntityManager.TryGetComponent<UseDelayComponent?>(used, out var delayComponent))
|
if (TryComp(used, out UseDelayComponent? delayComponent))
|
||||||
{
|
{
|
||||||
if (delayComponent.ActiveDelay)
|
if (delayComponent.ActiveDelay)
|
||||||
return;
|
return;
|
||||||
@@ -472,7 +631,7 @@ namespace Content.Shared.Interaction
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!EntityManager.TryGetComponent(used, out IActivate? activateComp))
|
if (!TryComp(used, out IActivate? activateComp))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var activateEventArgs = new ActivateEventArgs(user, used);
|
var activateEventArgs = new ActivateEventArgs(user, used);
|
||||||
@@ -506,7 +665,7 @@ namespace Content.Shared.Interaction
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void UseInteraction(EntityUid user, EntityUid used)
|
public void UseInteraction(EntityUid user, EntityUid used)
|
||||||
{
|
{
|
||||||
if (EntityManager.TryGetComponent<UseDelayComponent?>(used, out var delayComponent))
|
if (TryComp(used, out UseDelayComponent? delayComponent))
|
||||||
{
|
{
|
||||||
if (delayComponent.ActiveDelay)
|
if (delayComponent.ActiveDelay)
|
||||||
return;
|
return;
|
||||||
@@ -519,7 +678,7 @@ namespace Content.Shared.Interaction
|
|||||||
if (useMsg.Handled)
|
if (useMsg.Handled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var uses = EntityManager.GetComponents<IUse>(used).ToList();
|
var uses = AllComps<IUse>(used).ToList();
|
||||||
|
|
||||||
// Try to use item on any components which have the interface
|
// Try to use item on any components which have the interface
|
||||||
foreach (var use in uses)
|
foreach (var use in uses)
|
||||||
@@ -560,7 +719,7 @@ namespace Content.Shared.Interaction
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var comps = EntityManager.GetComponents<IThrown>(thrown).ToList();
|
var comps = AllComps<IThrown>(thrown).ToList();
|
||||||
var args = new ThrownEventArgs(user);
|
var args = new ThrownEventArgs(user);
|
||||||
|
|
||||||
// Call Thrown on all components that implement the interface
|
// Call Thrown on all components that implement the interface
|
||||||
@@ -584,7 +743,7 @@ namespace Content.Shared.Interaction
|
|||||||
if (equipMsg.Handled)
|
if (equipMsg.Handled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var comps = EntityManager.GetComponents<IEquipped>(equipped).ToList();
|
var comps = AllComps<IEquipped>(equipped).ToList();
|
||||||
|
|
||||||
// Call Thrown on all components that implement the interface
|
// Call Thrown on all components that implement the interface
|
||||||
foreach (var comp in comps)
|
foreach (var comp in comps)
|
||||||
@@ -604,7 +763,7 @@ namespace Content.Shared.Interaction
|
|||||||
if (unequipMsg.Handled)
|
if (unequipMsg.Handled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var comps = EntityManager.GetComponents<IUnequipped>(equipped).ToList();
|
var comps = AllComps<IUnequipped>(equipped).ToList();
|
||||||
|
|
||||||
// Call Thrown on all components that implement the interface
|
// Call Thrown on all components that implement the interface
|
||||||
foreach (var comp in comps)
|
foreach (var comp in comps)
|
||||||
@@ -625,7 +784,7 @@ namespace Content.Shared.Interaction
|
|||||||
if (equippedHandMessage.Handled)
|
if (equippedHandMessage.Handled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var comps = EntityManager.GetComponents<IEquippedHand>(item).ToList();
|
var comps = AllComps<IEquippedHand>(item).ToList();
|
||||||
|
|
||||||
foreach (var comp in comps)
|
foreach (var comp in comps)
|
||||||
{
|
{
|
||||||
@@ -644,7 +803,7 @@ namespace Content.Shared.Interaction
|
|||||||
if (unequippedHandMessage.Handled)
|
if (unequippedHandMessage.Handled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var comps = EntityManager.GetComponents<IUnequippedHand>(item).ToList();
|
var comps = AllComps<IUnequippedHand>(item).ToList();
|
||||||
|
|
||||||
foreach (var comp in comps)
|
foreach (var comp in comps)
|
||||||
{
|
{
|
||||||
@@ -681,9 +840,9 @@ namespace Content.Shared.Interaction
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EntityManager.GetComponent<TransformComponent>(item).LocalRotation = Angle.Zero;
|
Transform(item).LocalRotation = Angle.Zero;
|
||||||
|
|
||||||
var comps = EntityManager.GetComponents<IDropped>(item).ToList();
|
var comps = AllComps<IDropped>(item).ToList();
|
||||||
|
|
||||||
// Call Land on all components that implement the interface
|
// Call Land on all components that implement the interface
|
||||||
foreach (var comp in comps)
|
foreach (var comp in comps)
|
||||||
@@ -706,7 +865,7 @@ namespace Content.Shared.Interaction
|
|||||||
if (handSelectedMsg.Handled)
|
if (handSelectedMsg.Handled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var comps = EntityManager.GetComponents<IHandSelected>(item).ToList();
|
var comps = AllComps<IHandSelected>(item).ToList();
|
||||||
|
|
||||||
// Call Land on all components that implement the interface
|
// Call Land on all components that implement the interface
|
||||||
foreach (var comp in comps)
|
foreach (var comp in comps)
|
||||||
@@ -726,7 +885,7 @@ namespace Content.Shared.Interaction
|
|||||||
if (handDeselectedMsg.Handled)
|
if (handDeselectedMsg.Handled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var comps = EntityManager.GetComponents<IHandDeselected>(item).ToList();
|
var comps = AllComps<IHandDeselected>(item).ToList();
|
||||||
|
|
||||||
// Call Land on all components that implement the interface
|
// Call Land on all components that implement the interface
|
||||||
foreach (var comp in comps)
|
foreach (var comp in comps)
|
||||||
@@ -742,6 +901,36 @@ namespace Content.Shared.Interaction
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract bool CanAccessViaStorage(EntityUid user, EntityUid target);
|
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
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System;
|
using Content.Shared.Access.Components;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.Access.Components;
|
|
||||||
using Content.Shared.Containers.ItemSlots;
|
using Content.Shared.Containers.ItemSlots;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Prototypes;
|
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.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.PDA
|
namespace Content.Shared.PDA
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class PDAComponent : Component
|
public class PDAComponent : Component
|
||||||
65
Content.Shared/PDA/SharedPDASystem.cs
Normal file
65
Content.Shared/PDA/SharedPDASystem.cs
Normal file
@@ -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<PDAComponent, ComponentInit>(OnComponentInit);
|
||||||
|
SubscribeLocalEvent<PDAComponent, ComponentRemove>(OnComponentRemove);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<PDAComponent, EntInsertedIntoContainerMessage>(OnItemInserted);
|
||||||
|
SubscribeLocalEvent<PDAComponent, EntRemovedFromContainerMessage>(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<IdCardComponent>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
using Content.Shared.Administration.Logs;
|
|
||||||
using Content.Shared.Database;
|
|
||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
@@ -12,10 +10,35 @@ namespace Content.Shared.Verbs
|
|||||||
{
|
{
|
||||||
public abstract class SharedVerbSystem : EntitySystem
|
public abstract class SharedVerbSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SharedAdminLogSystem _logSystem = default!;
|
|
||||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeAllEvent<ExecuteVerbEvent>(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);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raises a number of events in order to get all verbs of the given type(s) defined in local systems. This
|
/// 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.
|
/// does not request verbs from the server.
|
||||||
@@ -92,60 +115,6 @@ namespace Content.Shared.Verbs
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This will try to call the action delegates and raise the local events for the given verb.
|
/// This will try to call the action delegates and raise the local events for the given verb.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public void ExecuteVerb(Verb verb, EntityUid user, EntityUid target, bool forced = false)
|
public abstract 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}");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace Content.Shared.Weapons.Melee
|
|||||||
public EntityCoordinates ClickLocation { get; }
|
public EntityCoordinates ClickLocation { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// UID of the entity that was attacked.
|
/// The entity that was attacked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public EntityUid? Target { get; }
|
public EntityUid? Target { get; }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user