Verb predict (#5638)

This commit is contained in:
Leon Friedrich
2021-12-16 23:42:02 +13:00
committed by GitHub
parent 2e141347ed
commit 7e49b22a74
40 changed files with 551 additions and 395 deletions

View File

@@ -0,0 +1,26 @@
using Content.Shared.Access.Systems;
using Content.Shared.PDA;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Shared.Access.Components
{
// TODO BUI NETWORKING if ever clients can open their own BUI's (id card console, pda), then this data should be
// networked.
[RegisterComponent]
[Friend(typeof(SharedIdCardSystem), typeof(SharedPDASystem))]
public class IdCardComponent : Component
{
public override string Name => "IdCard";
[DataField("originalOwnerName")]
public string OriginalOwnerName = default!;
[DataField("fullName")]
public string? FullName;
[DataField("jobTitle")]
public string? JobTitle;
}
}

View File

@@ -5,7 +5,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Shared.Access
namespace Content.Shared.Access.Components
{
public class SharedIdCardConsoleComponent : Component
{

View File

@@ -1,9 +1,10 @@
using Content.Shared.Access.Components;
using Content.Shared.Containers.ItemSlots;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Shared.Access
namespace Content.Shared.Access.Systems
{
[UsedImplicitly]
public abstract class SharedIdCardConsoleSystem : EntitySystem

View 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?
}

View File

@@ -0,0 +1,26 @@
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Shared.Chemistry.Components
{
/// 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>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>
/// <see cref="Dispenser.SharedReagentDispenserComponent"/>
[RegisterComponent]
[NetworkedComponent] // only needed for white-lists. Client doesn't actually need Solution data;
public class FitsInDispenserComponent : Component
{
public override string Name => "FitsInDispenser";
/// <summary>
/// Solution name that will interact with ReagentDispenserComponent.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("solution")]
public string Solution { get; set; } = "default";
}
}

View File

@@ -3,6 +3,7 @@ using Content.Shared.Acts;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Sound;
using Content.Shared.Verbs;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
@@ -12,6 +13,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -24,6 +26,7 @@ namespace Content.Shared.Containers.ItemSlots
{
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
public override void Initialize()
{
@@ -174,7 +177,7 @@ namespace Content.Shared.Containers.ItemSlots
if (slot.Item != null)
hands.TryPutInAnyHand(slot.Item.Value);
Insert(uid, slot, args.Used);
Insert(uid, slot, args.Used, args.User, true);
args.Handled = true;
return;
}
@@ -186,13 +189,32 @@ namespace Content.Shared.Containers.ItemSlots
/// Insert an item into a slot. This does not perform checks, so make sure to also use <see
/// cref="CanInsert"/> or just use <see cref="TryInsert"/> instead.
/// </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);
// ContainerSlot automatically raises a directed EntInsertedIntoContainerMessage
if (slot.InsertSound != null)
SoundSystem.Play(Filter.Pvs(uid), slot.InsertSound.GetSound(), uid, slot.SoundOptions);
PlaySound(uid, slot.InsertSound, slot.SoundOptions, userAudio ? null : user);
}
/// <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>
@@ -228,7 +250,7 @@ namespace Content.Shared.Containers.ItemSlots
/// Tries to insert item into a specific slot.
/// </summary>
/// <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))
return false;
@@ -236,19 +258,19 @@ namespace Content.Shared.Containers.ItemSlots
if (!itemSlots.Slots.TryGetValue(id, out var slot))
return false;
return TryInsert(uid, slot, item);
return TryInsert(uid, slot, item, user);
}
/// <summary>
/// Tries to insert item into a specific slot.
/// </summary>
/// <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))
return false;
Insert(uid, slot, item);
Insert(uid, slot, item, user);
return true;
}
@@ -271,7 +293,7 @@ namespace Content.Shared.Containers.ItemSlots
if (!_actionBlockerSystem.CanInteract(user) && hands.Drop(item))
return false;
Insert(uid, slot, item);
Insert(uid, slot, item, user);
return true;
}
#endregion
@@ -281,20 +303,19 @@ namespace Content.Shared.Containers.ItemSlots
/// Eject an item into a slot. This does not perform checks (e.g., is the slot locked?), so you should
/// probably just use <see cref="TryEject"/> instead.
/// </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);
// ContainerSlot automatically raises a directed EntRemovedFromContainerMessage
if (slot.EjectSound != null)
SoundSystem.Play(Filter.Pvs(uid), slot.EjectSound.GetSound(), uid, slot.SoundOptions);
PlaySound(uid, slot.EjectSound, slot.SoundOptions, user);
}
/// <summary>
/// Try to eject an item from a slot.
/// </summary>
/// <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;
@@ -302,7 +323,7 @@ namespace Content.Shared.Containers.ItemSlots
return false;
item = slot.Item;
Eject(uid, slot, item.Value);
Eject(uid, slot, item.Value, user);
return true;
}
@@ -310,7 +331,8 @@ namespace Content.Shared.Containers.ItemSlots
/// Try to eject item from a slot.
/// </summary>
/// <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;
@@ -320,7 +342,7 @@ namespace Content.Shared.Containers.ItemSlots
if (!itemSlots.Slots.TryGetValue(id, out var slot))
return false;
return TryEject(uid, slot, out item);
return TryEject(uid, slot, user, out item);
}
/// <summary>
@@ -333,7 +355,7 @@ namespace Content.Shared.Containers.ItemSlots
/// </returns>
public bool TryEjectToHands(EntityUid uid, ItemSlot slot, EntityUid? user)
{
if (!TryEject(uid, slot, out var item))
if (!TryEject(uid, slot, user, out var item))
return false;
if (user != null && EntityManager.TryGetComponent(user.Value, out SharedHandsComponent? hands))
@@ -428,7 +450,7 @@ namespace Content.Shared.Containers.ItemSlots
: EntityManager.GetComponent<MetaDataComponent>(args.Using.Value).EntityName ?? string.Empty;
Verb insertVerb = new();
insertVerb.Act = () => Insert(uid, slot, args.Using.Value);
insertVerb.Act = () => Insert(uid, slot, args.Using.Value, args.User);
if (slot.InsertVerbText != null)
{
@@ -461,7 +483,7 @@ namespace Content.Shared.Containers.ItemSlots
foreach (var slot in component.Slots.Values)
{
if (slot.EjectOnBreak && slot.HasItem)
TryEject(uid, slot, out var _);
TryEject(uid, slot, null, out var _);
}
}

View File

@@ -1,12 +1,16 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Content.Shared.ActionBlocker;
using Content.Shared.Administration.Logs;
using Content.Shared.CombatMode;
using Content.Shared.Database;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Input;
using Content.Shared.Interaction.Helpers;
using Content.Shared.Inventory;
using Content.Shared.Physics;
using Content.Shared.Popups;
@@ -16,13 +20,14 @@ using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Input.Binding;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
#pragma warning disable 618
@@ -39,12 +44,166 @@ namespace Content.Shared.Interaction
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly SharedVerbSystem _verbSystem = default!;
[Dependency] private readonly SharedAdminLogSystem _adminLogSystem = default!;
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
public const float InteractionRange = 2;
public const float InteractionRangeSquared = InteractionRange * InteractionRange;
public delegate bool Ignored(EntityUid entity);
public override void Initialize()
{
SubscribeAllEvent<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>
/// Traces a ray from coords to otherCoords and returns the length
/// of the vector between coords and the ray's first hit.
@@ -154,7 +313,7 @@ namespace Content.Shared.Interaction
foreach (var result in rayResults)
{
if (!EntityManager.TryGetComponent(result.HitEntity, out IPhysBody? p))
if (!TryComp(result.HitEntity, out IPhysBody? p))
{
continue;
}
@@ -213,7 +372,7 @@ namespace Content.Shared.Interaction
bool popup = false)
{
predicate ??= e => e == origin || e == other;
return InRangeUnobstructed(origin, EntityManager.GetComponent<TransformComponent>(other).MapPosition, range, collisionMask, predicate, ignoreInsideBlocker, popup);
return InRangeUnobstructed(origin, Transform(other).MapPosition, range, collisionMask, predicate, ignoreInsideBlocker, popup);
}
/// <summary>
@@ -345,7 +504,7 @@ namespace Content.Shared.Interaction
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originPosition = EntityManager.GetComponent<TransformComponent>(origin).MapPosition;
var originPosition = Transform(origin).MapPosition;
predicate ??= e => e == origin;
var inRange = InRangeUnobstructed(originPosition, other, range, collisionMask, predicate, ignoreInsideBlocker);
@@ -392,7 +551,7 @@ namespace Content.Shared.Interaction
var interactUsingEventArgs = new InteractUsingEventArgs(user, clickLocation, used, target);
var interactUsings = EntityManager.GetComponents<IInteractUsing>(target).OrderByDescending(x => x.Priority);
var interactUsings = AllComps<IInteractUsing>(target).OrderByDescending(x => x.Priority);
foreach (var interactUsing in interactUsings)
{
// If an InteractUsing returns a status completion we finish our interaction
@@ -418,7 +577,7 @@ namespace Content.Shared.Interaction
return true;
var afterInteractEventArgs = new AfterInteractEventArgs(user, clickLocation, target, canReach);
var afterInteracts = EntityManager.GetComponents<IAfterInteract>(used).OrderByDescending(x => x.Priority).ToList();
var afterInteracts = AllComps<IAfterInteract>(used).OrderByDescending(x => x.Priority).ToList();
foreach (var afterInteract in afterInteracts)
{
@@ -444,7 +603,7 @@ namespace Content.Shared.Interaction
protected void InteractionActivate(EntityUid user, EntityUid used)
{
if (EntityManager.TryGetComponent<UseDelayComponent?>(used, out var delayComponent))
if (TryComp(used, out UseDelayComponent? delayComponent))
{
if (delayComponent.ActiveDelay)
return;
@@ -472,7 +631,7 @@ namespace Content.Shared.Interaction
return;
}
if (!EntityManager.TryGetComponent(used, out IActivate? activateComp))
if (!TryComp(used, out IActivate? activateComp))
return;
var activateEventArgs = new ActivateEventArgs(user, used);
@@ -506,7 +665,7 @@ namespace Content.Shared.Interaction
/// </summary>
public void UseInteraction(EntityUid user, EntityUid used)
{
if (EntityManager.TryGetComponent<UseDelayComponent?>(used, out var delayComponent))
if (TryComp(used, out UseDelayComponent? delayComponent))
{
if (delayComponent.ActiveDelay)
return;
@@ -519,7 +678,7 @@ namespace Content.Shared.Interaction
if (useMsg.Handled)
return;
var uses = EntityManager.GetComponents<IUse>(used).ToList();
var uses = AllComps<IUse>(used).ToList();
// Try to use item on any components which have the interface
foreach (var use in uses)
@@ -560,7 +719,7 @@ namespace Content.Shared.Interaction
return;
}
var comps = EntityManager.GetComponents<IThrown>(thrown).ToList();
var comps = AllComps<IThrown>(thrown).ToList();
var args = new ThrownEventArgs(user);
// Call Thrown on all components that implement the interface
@@ -584,7 +743,7 @@ namespace Content.Shared.Interaction
if (equipMsg.Handled)
return;
var comps = EntityManager.GetComponents<IEquipped>(equipped).ToList();
var comps = AllComps<IEquipped>(equipped).ToList();
// Call Thrown on all components that implement the interface
foreach (var comp in comps)
@@ -604,7 +763,7 @@ namespace Content.Shared.Interaction
if (unequipMsg.Handled)
return;
var comps = EntityManager.GetComponents<IUnequipped>(equipped).ToList();
var comps = AllComps<IUnequipped>(equipped).ToList();
// Call Thrown on all components that implement the interface
foreach (var comp in comps)
@@ -625,7 +784,7 @@ namespace Content.Shared.Interaction
if (equippedHandMessage.Handled)
return;
var comps = EntityManager.GetComponents<IEquippedHand>(item).ToList();
var comps = AllComps<IEquippedHand>(item).ToList();
foreach (var comp in comps)
{
@@ -644,7 +803,7 @@ namespace Content.Shared.Interaction
if (unequippedHandMessage.Handled)
return;
var comps = EntityManager.GetComponents<IUnequippedHand>(item).ToList();
var comps = AllComps<IUnequippedHand>(item).ToList();
foreach (var comp in comps)
{
@@ -681,9 +840,9 @@ namespace Content.Shared.Interaction
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
foreach (var comp in comps)
@@ -706,7 +865,7 @@ namespace Content.Shared.Interaction
if (handSelectedMsg.Handled)
return;
var comps = EntityManager.GetComponents<IHandSelected>(item).ToList();
var comps = AllComps<IHandSelected>(item).ToList();
// Call Land on all components that implement the interface
foreach (var comp in comps)
@@ -726,7 +885,7 @@ namespace Content.Shared.Interaction
if (handDeselectedMsg.Handled)
return;
var comps = EntityManager.GetComponents<IHandDeselected>(item).ToList();
var comps = AllComps<IHandDeselected>(item).ToList();
// Call Land on all components that implement the interface
foreach (var comp in comps)
@@ -742,6 +901,36 @@ namespace Content.Shared.Interaction
/// </summary>
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
}

View File

@@ -0,0 +1,33 @@
using Content.Shared.Access.Components;
using Content.Shared.Containers.ItemSlots;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.ViewVariables;
namespace Content.Shared.PDA
{
[RegisterComponent]
public class PDAComponent : Component
{
public override string Name => "PDA";
[DataField("idSlot")]
public ItemSlot IdSlot = new();
[DataField("penSlot")]
public ItemSlot PenSlot = new();
// Really this should just be using ItemSlot.StartingItem. However, seeing as we have so many different starting
// PDA's and no nice way to inherit the other fields from the ItemSlot data definition, this makes the yaml much
// nicer to read.
[DataField("idCard", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string? IdCard;
[ViewVariables] public IdCardComponent? ContainedID;
[ViewVariables] public bool FlashlightOn;
[ViewVariables] public string? OwnerName;
}
}

View 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);
}
}
}

View File

@@ -1,7 +1,5 @@
using System.Collections.Generic;
using Content.Shared.ActionBlocker;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Robust.Shared.Containers;
@@ -12,10 +10,35 @@ namespace Content.Shared.Verbs
{
public abstract class SharedVerbSystem : EntitySystem
{
[Dependency] private readonly SharedAdminLogSystem _logSystem = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeAllEvent<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>
/// Raises a number of events in order to get all verbs of the given type(s) defined in local systems. This
/// does not request verbs from the server.
@@ -92,60 +115,6 @@ namespace Content.Shared.Verbs
/// <remarks>
/// This will try to call the action delegates and raise the local events for the given verb.
/// </remarks>
public void ExecuteVerb(Verb verb, EntityUid user, EntityUid target, bool forced = false)
{
// first, lets log the verb. Just in case it ends up crashing the server or something.
LogVerb(verb, user, target, forced);
// then invoke any relevant actions
verb.Act?.Invoke();
// Maybe raise a local event
if (verb.ExecutionEventArgs != null)
{
if (verb.EventTarget.IsValid())
RaiseLocalEvent(verb.EventTarget, verb.ExecutionEventArgs);
else
RaiseLocalEvent(verb.ExecutionEventArgs);
}
}
public void LogVerb(Verb verb, EntityUid user, EntityUid target, bool forced)
{
// first get the held item. again.
EntityUid usedUid = default;
if (EntityManager.TryGetComponent(user, out SharedHandsComponent? hands) &&
hands.TryGetActiveHeldEntity(out var heldEntity))
{
usedUid = heldEntity;
if (usedUid != default && EntityManager.TryGetComponent(usedUid, out HandVirtualItemComponent? pull))
usedUid = pull.BlockingEntity;
}
// get all the entities
if (!user.IsValid() || !target.IsValid())
return;
EntityUid? used = null;
if (usedUid != default)
EntityManager.EntityExists(usedUid);
var verbText = $"{verb.Category?.Text} {verb.Text}".Trim();
// lets not frame people, eh?
var executionText = forced ? "was forced to execute" : "executed";
if (used == null)
{
_logSystem.Add(LogType.Verb, verb.Impact,
$"{ToPrettyString(user):user} {executionText} the [{verbText:verb}] verb targeting {ToPrettyString(target):target}");
}
else
{
_logSystem.Add(LogType.Verb, verb.Impact,
$"{ToPrettyString(user):user} {executionText} the [{verbText:verb}] verb targeting {ToPrettyString(target):target} while holding {ToPrettyString(used.Value):held}");
}
}
public abstract void ExecuteVerb(Verb verb, EntityUid user, EntityUid target, bool forced = false);
}
}

View File

@@ -25,7 +25,7 @@ namespace Content.Shared.Weapons.Melee
public EntityCoordinates ClickLocation { get; }
/// <summary>
/// UID of the entity that was attacked.
/// The entity that was attacked.
/// </summary>
public EntityUid? Target { get; }