Files
crystall-punk-14/Content.Shared/Interaction/SharedInteractionSystem.cs

1181 lines
49 KiB
C#
Raw Normal View History

2021-12-16 23:42:02 +13:00
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.ActionBlocker;
using Content.Shared.Administration;
using Content.Shared.Administration.Logs;
using Content.Shared.Administration.Managers;
2021-12-16 23:42:02 +13:00
using Content.Shared.CombatMode;
2021-11-28 14:56:53 +01:00
using Content.Shared.Database;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
2021-12-16 23:42:02 +13:00
using Content.Shared.Input;
using Content.Shared.Interaction.Components;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Physics;
2021-09-26 15:18:45 +02:00
using Content.Shared.Popups;
using Content.Shared.Tag;
using Content.Shared.Timing;
using Content.Shared.Verbs;
using Content.Shared.Wall;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.Input;
2021-12-16 23:42:02 +13:00
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
2022-03-12 12:53:42 +13:00
using Robust.Shared.Timing;
#pragma warning disable 618
2021-06-09 22:19:39 +02:00
namespace Content.Shared.Interaction
{
/// <summary>
/// Governs interactions during clicking on entities
/// </summary>
[UsedImplicitly]
public abstract partial class SharedInteractionSystem : EntitySystem
{
2022-03-12 12:53:42 +13:00
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ISharedAdminManager _adminManager = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
2021-12-16 23:42:02 +13:00
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedVerbSystem _verbSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
2022-03-09 20:12:17 +13:00
[Dependency] private readonly UseDelaySystem _useDelay = default!;
[Dependency] private readonly PullingSystem _pullSystem = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
2024-01-03 21:33:09 -04:00
private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable;
public const float InteractionRange = 1.5f;
public const float InteractionRangeSquared = InteractionRange * InteractionRange;
public const float MaxRaycastRange = 100f;
2021-12-03 11:15:41 -08:00
public delegate bool Ignored(EntityUid entity);
2021-12-16 23:42:02 +13:00
public override void Initialize()
{
SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt);
2021-12-16 23:42:02 +13:00
SubscribeAllEvent<InteractInventorySlotEvent>(HandleInteractInventorySlotEvent);
SubscribeLocalEvent<UnremoveableComponent, ContainerGettingRemovedAttemptEvent>(OnRemoveAttempt);
SubscribeLocalEvent<UnremoveableComponent, GotUnequippedEvent>(OnUnequip);
SubscribeLocalEvent<UnremoveableComponent, GotUnequippedHandEvent>(OnUnequipHand);
SubscribeLocalEvent<UnremoveableComponent, DroppedEvent>(OnDropped);
2021-12-16 23:42:02 +13:00
CommandBinds.Builder
.Bind(ContentKeyFunctions.AltActivateItemInWorld,
new PointerInputCmdHandler(HandleAltUseInteraction))
2022-03-09 20:12:17 +13:00
.Bind(EngineKeyFunctions.Use,
new PointerInputCmdHandler(HandleUseInteraction))
.Bind(ContentKeyFunctions.ActivateItemInWorld,
new PointerInputCmdHandler(HandleActivateItemInWorld))
2022-10-19 01:06:44 +02:00
.Bind(ContentKeyFunctions.TryPullObject,
new PointerInputCmdHandler(HandleTryPullObject))
2021-12-16 23:42:02 +13:00
.Register<SharedInteractionSystem>();
InitializeBlocking();
2021-12-16 23:42:02 +13:00
}
public override void Shutdown()
{
CommandBinds.Unregister<SharedInteractionSystem>();
base.Shutdown();
}
/// <summary>
/// Check that the user that is interacting with the BUI is capable of interacting and can access the entity.
/// </summary>
private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev)
{
if (ev.Sender.AttachedEntity is not { } user || !_actionBlockerSystem.CanInteract(user, ev.Target))
{
ev.Cancel();
return;
}
// Check if the bound entity is accessible. Note that we allow admins to ignore this restriction, so that
// they can fiddle with UI's that people can't normally interact with (e.g., placing things directly into
// other people's backpacks).
if (!_containerSystem.IsInSameOrParentContainer(user, ev.Target)
&& !CanAccessViaStorage(user, ev.Target)
&& !_adminManager.HasAdminFlag(user, AdminFlags.Admin))
{
ev.Cancel();
return;
}
if (!InRangeUnobstructed(user, ev.Target))
{
ev.Cancel();
}
}
/// <summary>
/// Prevents an item with the Unremovable component from being removed from a container by almost any means
/// </summary>
private void OnRemoveAttempt(EntityUid uid, UnremoveableComponent item, ContainerGettingRemovedAttemptEvent args)
{
args.Cancel();
}
/// <summary>
/// If item has DeleteOnDrop true then item will be deleted if removed from inventory, if it is false then item
/// loses Unremoveable when removed from inventory (gibbing).
/// </summary>
private void OnUnequip(EntityUid uid, UnremoveableComponent item, GotUnequippedEvent args)
{
if (!item.DeleteOnDrop)
RemCompDeferred<UnremoveableComponent>(uid);
else if (_net.IsServer)
QueueDel(uid);
}
private void OnUnequipHand(EntityUid uid, UnremoveableComponent item, GotUnequippedHandEvent args)
{
if (!item.DeleteOnDrop)
RemCompDeferred<UnremoveableComponent>(uid);
else if (_net.IsServer)
QueueDel(uid);
}
private void OnDropped(EntityUid uid, UnremoveableComponent item, DroppedEvent args)
{
if (!item.DeleteOnDrop)
RemCompDeferred<UnremoveableComponent>(uid);
else if (_net.IsServer)
QueueDel(uid);
}
2022-10-19 01:06:44 +02:00
private bool HandleTryPullObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
if (!ValidateClientInput(session, coords, uid, out var userEntity))
{
Log.Info($"TryPullObject input validation failed");
2022-10-19 01:06:44 +02:00
return true;
}
//is this user trying to pull themself?
if (userEntity.Value == uid)
return false;
if (Deleted(uid))
return false;
if (!InRangeUnobstructed(userEntity.Value, uid, popup: true))
return false;
if (!TryComp(uid, out PullableComponent? pull))
2022-10-19 01:06:44 +02:00
return false;
_pullSystem.TogglePull(uid, userEntity.Value, pull);
2022-10-19 01:06:44 +02:00
return false;
}
2021-12-16 23:42:02 +13:00
/// <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 item = GetEntity(msg.ItemUid);
2021-12-16 23:42:02 +13:00
// client sanitization
if (!TryComp(item, out TransformComponent? itemXform) || !ValidateClientInput(args.SenderSession, itemXform.Coordinates, item, out var user))
2021-12-16 23:42:02 +13:00
{
Log.Info($"Inventory interaction validation failed. Session={args.SenderSession}");
2021-12-16 23:42:02 +13:00
return;
}
// We won't bother to check that the target item is ACTUALLY in an inventory slot. UserInteraction() and
// InteractionActivate() should check that the item is accessible. So.. if a user wants to lie about an
// in-reach item being used in a slot... that should have no impact. This is functionally the same as if
// they had somehow directly clicked on that item.
2021-12-16 23:42:02 +13:00
if (msg.AltInteract)
// Use 'UserInteraction' function - behaves as if the user alt-clicked the item in the world.
UserInteraction(user.Value, itemXform.Coordinates, item, msg.AltInteract);
2021-12-16 23:42:02 +13:00
else
// User used 'E'. We want to activate it, not simulate clicking on the item
InteractionActivate(user.Value, item);
2021-12-16 23:42:02 +13:00
}
public bool HandleAltUseInteraction(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
// client sanitization
if (!ValidateClientInput(session, coords, uid, out var user))
{
Log.Info($"Alt-use input validation failed");
2021-12-16 23:42:02 +13:00
return true;
}
UserInteraction(user.Value, coords, uid, altInteract: true, checkAccess: ShouldCheckAccess(user.Value));
2021-12-16 23:42:02 +13:00
return false;
}
2022-03-09 20:12:17 +13:00
public bool HandleUseInteraction(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
// client sanitization
if (!ValidateClientInput(session, coords, uid, out var userEntity))
{
Log.Info($"Use input validation failed");
2022-03-09 20:12:17 +13:00
return true;
}
UserInteraction(userEntity.Value, coords, !Deleted(uid) ? uid : null, checkAccess: ShouldCheckAccess(userEntity.Value));
2022-03-09 20:12:17 +13:00
return false;
}
private bool ShouldCheckAccess(EntityUid user)
{
// This is for Admin/mapping convenience. If ever there are other ghosts that can still interact, this check
// might need to be more selective.
return !_tagSystem.HasTag(user, "BypassInteractionRangeChecks");
}
/// <summary>
/// Returns true if the specified entity should hand interact with the target instead of attacking
/// </summary>
/// <param name="user">The user interacting in combat mode</param>
/// <param name="target">The target of the interaction</param>
/// <returns></returns>
public bool CombatModeCanHandInteract(EntityUid user, EntityUid? target)
{
// Always allow attack in these cases
if (target == null || !TryComp<HandsComponent>(user, out var hands) || hands.ActiveHand?.HeldEntity is not null)
return false;
// Only eat input if:
// - Target isn't an item
// - Target doesn't cancel should-interact event
// This is intended to allow items to be picked up in combat mode,
// but to also allow items to force attacks anyway (like mobs which are items, e.g. mice)
if (!HasComp<ItemComponent>(target))
return false;
var combatEv = new CombatModeShouldHandInteractEvent();
RaiseLocalEvent(target.Value, ref combatEv);
if (combatEv.Cancelled)
return false;
return true;
}
2021-12-16 23:42:02 +13:00
/// <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 void UserInteraction(
EntityUid user,
EntityCoordinates coordinates,
EntityUid? target,
bool altInteract = false,
bool checkCanInteract = true,
bool checkAccess = true,
bool checkCanUse = true)
2021-12-16 23:42:02 +13:00
{
if (TryComp<InteractionRelayComponent>(user, out var relay) && relay.RelayEntity is not null)
{
2023-03-24 14:42:43 +13:00
// TODO this needs to be handled better. This probably bypasses many complex can-interact checks in weird roundabout ways.
if (_actionBlockerSystem.CanInteract(user, target))
{
UserInteraction(relay.RelayEntity.Value, coordinates, target, altInteract, checkCanInteract,
checkAccess, checkCanUse);
2023-03-24 14:42:43 +13:00
return;
}
}
2021-12-16 23:42:02 +13:00
if (target != null && Deleted(target.Value))
return;
if (!altInteract && TryComp<CombatModeComponent>(user, out var combatMode) && combatMode.IsInCombatMode)
2021-12-16 23:42:02 +13:00
{
if (!CombatModeCanHandInteract(user, target))
return;
2021-12-16 23:42:02 +13:00
}
if (!ValidateInteractAndFace(user, coordinates))
return;
if (altInteract && target != null)
{
// Perform alternative interactions, using context menu verbs.
// These perform their own range, can-interact, and accessibility checks.
AltInteract(user, target.Value);
2022-02-15 23:40:48 +13:00
return;
}
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, target))
2021-12-16 23:42:02 +13:00
return;
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
// Also checks if the item is accessible via some storage UI (e.g., open backpack)
if (checkAccess
&& target != null
&& !_containerSystem.IsInSameOrParentContainer(user, target.Value)
&& !CanAccessViaStorage(user, target.Value))
2021-12-16 23:42:02 +13:00
return;
var inRangeUnobstructed = target == null
? !checkAccess || InRangeUnobstructed(user, coordinates)
: !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities
// Does the user have hands?
if (!TryComp<HandsComponent>(user, out var hands) || hands.ActiveHand == null)
2022-08-13 09:49:41 -04:00
{
var ev = new InteractNoHandEvent(user, target, coordinates);
RaiseLocalEvent(user, ev);
2022-08-13 09:49:41 -04:00
if (target != null)
{
var interactedEv = new InteractedNoHandEvent(target.Value, user, coordinates);
RaiseLocalEvent(target.Value, interactedEv);
DoContactInteraction(user, target.Value, ev);
2022-08-13 09:49:41 -04:00
}
2021-12-16 23:42:02 +13:00
return;
2022-08-13 09:49:41 -04:00
}
2021-12-16 23:42:02 +13:00
// empty-hand interactions
// combat mode hand interactions will always be true here -- since
// they check this earlier before returning in
if (hands.ActiveHandEntity is not { } held)
{
if (inRangeUnobstructed && target != null)
InteractHand(user, target.Value);
2021-12-16 23:42:02 +13:00
return;
}
2021-12-30 18:27:15 -08:00
// Can the user use the held entity?
if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user))
return;
2022-03-17 20:13:31 +13:00
if (target == held)
{
UseInHandInteraction(user, target.Value, checkCanUse: false, checkCanInteract: false);
return;
}
if (inRangeUnobstructed && target != null)
2022-01-01 16:16:45 +13:00
{
InteractUsing(
user,
2022-03-17 20:13:31 +13:00
held,
target.Value,
coordinates,
checkCanInteract: false,
checkCanUse: false);
return;
2021-12-16 23:42:02 +13:00
}
InteractUsingRanged(
user,
2022-03-17 20:13:31 +13:00
held,
target,
coordinates,
inRangeUnobstructed);
2021-12-16 23:42:02 +13:00
}
2022-03-09 20:12:17 +13:00
public void InteractHand(EntityUid user, EntityUid target)
2021-12-16 23:42:02 +13:00
{
// allow for special logic before main interaction
var ev = new BeforeInteractHandEvent(target);
RaiseLocalEvent(user, ev);
if (ev.Handled)
{
_adminLogger.Add(LogType.InteractHand, LogImpact.Low, $"{ToPrettyString(user):user} interacted with {ToPrettyString(target):target}, but it was handled by another system");
return;
}
2022-03-09 20:12:17 +13:00
// all interactions should only happen when in range / unobstructed, so no range check is needed
var message = new InteractHandEvent(user, target);
RaiseLocalEvent(target, message, true);
_adminLogger.Add(LogType.InteractHand, LogImpact.Low, $"{ToPrettyString(user):user} interacted with {ToPrettyString(target):target}");
DoContactInteraction(user, target, message);
2022-03-09 20:12:17 +13:00
if (message.Handled)
return;
// Else we run Activate.
InteractionActivate(user, target,
checkCanInteract: false,
checkUseDelay: true,
checkAccess: false);
2021-12-16 23:42:02 +13:00
}
2022-03-09 20:12:17 +13:00
public void InteractUsingRanged(EntityUid user, EntityUid used, EntityUid? target,
2021-12-16 23:42:02 +13:00
EntityCoordinates clickLocation, bool inRangeUnobstructed)
{
2022-03-09 20:12:17 +13:00
if (RangedInteractDoBefore(user, used, target, clickLocation, inRangeUnobstructed))
return;
if (target != null)
{
var rangedMsg = new RangedInteractEvent(user, used, target.Value, clickLocation);
RaiseLocalEvent(target.Value, rangedMsg, true);
2022-03-09 20:12:17 +13:00
// We contact the USED entity, but not the target.
DoContactInteraction(user, used, rangedMsg);
2022-03-09 20:12:17 +13:00
if (rangedMsg.Handled)
return;
}
InteractDoAfter(user, used, target, clickLocation, inRangeUnobstructed);
2021-12-16 23:42:02 +13:00
}
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;
if (!HasComp<NoRotateOnInteractComponent>(user))
_rotateToFaceSystem.TryFaceCoordinates(user, coordinates.ToMapPos(EntityManager, _transform));
2021-12-16 23:42:02 +13:00
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.
/// </summary>
/// <param name="origin">Set of coordinates to use.</param>
/// <param name="other">Other set of coordinates to use.</param>
/// <param name="collisionMask">the mask to check for collisions</param>
/// <param name="predicate">
/// A predicate to check whether to ignore an entity or not.
/// If it returns true, it will be ignored.
/// </param>
/// <returns>Length of resulting ray.</returns>
public float UnobstructedDistance(
MapCoordinates origin,
MapCoordinates other,
int collisionMask = (int) InRangeUnobstructedMask,
Ignored? predicate = null)
{
var dir = other.Position - origin.Position;
if (dir.LengthSquared().Equals(0f))
return 0f;
predicate ??= _ => false;
var ray = new CollisionRay(origin.Position, dir.Normalized(), collisionMask);
var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, dir.Length(), predicate.Invoke, false).ToList();
if (rayResults.Count == 0)
return dir.Length();
return (rayResults[0].HitPos - origin.Position).Length();
}
/// <summary>
/// Checks that these coordinates are within a certain distance without any
/// entity that matches the collision mask obstructing them.
/// If the <paramref name="range"/> is zero or negative,
/// this method will only check if nothing obstructs the two sets
/// of coordinates.
/// </summary>
/// <param name="origin">Set of coordinates to use.</param>
/// <param name="other">Other set of coordinates to use.</param>
/// <param name="range">
/// Maximum distance between the two sets of coordinates.
/// </param>
/// <param name="collisionMask">The mask to check for collisions.</param>
/// <param name="predicate">
/// A predicate to check whether to ignore an entity or not.
/// If it returns true, it will be ignored.
/// </param>
/// <param name="checkAccess">Perform range checks</param>
/// <returns>
/// True if the two points are within a given range without being obstructed.
/// </returns>
public bool InRangeUnobstructed(
MapCoordinates origin,
MapCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = InRangeUnobstructedMask,
Ignored? predicate = null,
bool checkAccess = true)
{
// Have to be on same map regardless.
if (other.MapId != origin.MapId)
return false;
if (!checkAccess)
return true;
var dir = other.Position - origin.Position;
var length = dir.Length();
// If range specified also check it
if (range > 0f && length > range)
return false;
if (MathHelper.CloseTo(length, 0))
return true;
predicate ??= _ => false;
if (length > MaxRaycastRange)
{
Log.Warning("InRangeUnobstructed check performed over extreme range. Limiting CollisionRay size.");
length = MaxRaycastRange;
}
var ray = new CollisionRay(origin.Position, dir.Normalized(), (int) collisionMask);
var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList();
return rayResults.Count == 0;
}
public bool InRangeUnobstructed(
EntityUid origin,
EntityUid other,
float range = InteractionRange,
CollisionGroup collisionMask = InRangeUnobstructedMask,
Ignored? predicate = null,
bool popup = false)
{
if (!TryComp<TransformComponent>(other, out var otherXform))
return false;
return InRangeUnobstructed(origin, other, otherXform.Coordinates, otherXform.LocalRotation, range, collisionMask, predicate,
popup);
}
/// <summary>
/// Checks that two entities are within a certain distance without any
/// entity that matches the collision mask obstructing them.
/// If the <paramref name="range"/> is zero or negative,
/// this method will only check if nothing obstructs the two entities.
/// This function will also check whether the other entity is a wall-mounted entity. If it is, it will
/// automatically ignore some obstructions.
/// </summary>
/// <param name="origin">The first entity to use.</param>
/// <param name="other">Other entity to use.</param>
/// <param name="otherAngle">The local rotation to use for the other entity.</param>
/// <param name="range">
/// Maximum distance between the two entities.
/// </param>
/// <param name="collisionMask">The mask to check for collisions.</param>
/// <param name="predicate">
/// A predicate to check whether to ignore an entity or not.
/// If it returns true, it will be ignored.
/// </param>
/// <param name="popup">
/// Whether or not to popup a feedback message on the origin entity for
/// it to see.
/// </param>
/// <param name="otherCoordinates">The coordinates to use for the other entity.</param>
/// <returns>
/// True if the two points are within a given range without being obstructed.
/// </returns>
public bool InRangeUnobstructed(
2021-12-04 12:35:33 +01:00
EntityUid origin,
EntityUid other,
EntityCoordinates otherCoordinates,
Angle otherAngle,
float range = InteractionRange,
CollisionGroup collisionMask = InRangeUnobstructedMask,
Ignored? predicate = null,
bool popup = false)
{
Ignored combinedPredicate = e => e == origin || (predicate?.Invoke(e) ?? false);
var inRange = true;
MapCoordinates originPos = default;
var targetPos = otherCoordinates.ToMap(EntityManager, _transform);
Angle targetRot = default;
// So essentially:
// 1. If fixtures available check nearest point. We take in coordinates / angles because we might want to use a lag compensated position
// 2. Fall back to centre of body.
// Alternatively we could check centre distances first though
// that means we wouldn't be able to easily check overlap interactions.
if (range > 0f &&
TryComp<FixturesComponent>(origin, out var fixtureA) &&
2022-10-07 17:12:46 +11:00
// These fixture counts are stuff that has the component but no fixtures for <reasons> (e.g. buttons).
// At least until they get removed.
fixtureA.FixtureCount > 0 &&
TryComp<FixturesComponent>(other, out var fixtureB) &&
2022-10-07 17:12:46 +11:00
fixtureB.FixtureCount > 0 &&
TryComp<TransformComponent>(origin, out var xformA))
{
var (worldPosA, worldRotA) = xformA.GetWorldPositionRotation();
var xfA = new Transform(worldPosA, worldRotA);
var parentRotB = _transform.GetWorldRotation(otherCoordinates.EntityId);
var xfB = new Transform(targetPos.Position, parentRotB + otherAngle);
// Different map or the likes.
if (!_sharedBroadphaseSystem.TryGetNearest(origin, other,
out _, out _, out var distance,
xfA, xfB, fixtureA, fixtureB))
{
inRange = false;
}
// Overlap, early out and no raycast.
else if (distance.Equals(0f))
{
return true;
}
// Entity can bypass range checks.
else if (!ShouldCheckAccess(origin))
{
return true;
}
// Out of range so don't raycast.
else if (distance > range)
{
inRange = false;
}
else
{
// We'll still do the raycast from the centres but we'll bump the range as we know they're in range.
originPos = xformA.MapPosition;
range = (originPos.Position - targetPos.Position).Length();
}
}
2022-10-21 00:20:52 +11:00
// No fixtures, e.g. wallmounts.
else
{
originPos = Transform(origin).MapPosition;
var otherParent = Transform(other).ParentUid;
targetRot = otherParent.IsValid() ? Transform(otherParent).LocalRotation + otherAngle : otherAngle;
}
// Do a raycast to check if relevant
if (inRange)
{
var rayPredicate = GetPredicate(originPos, other, targetPos, targetRot, collisionMask, combinedPredicate);
inRange = InRangeUnobstructed(originPos, targetPos, range, collisionMask, rayPredicate, ShouldCheckAccess(origin));
}
2022-03-12 12:53:42 +13:00
if (!inRange && popup && _gameTiming.IsFirstTimePredicted)
{
var message = Loc.GetString("interaction-system-user-interaction-cannot-reach");
Upstream suka (#45) * Make BaseMedicalPDA abstract (#26567) * Fix GasMixers/Filters not working (#26568) * Fix GasMixers/Filters not working * OKAY GAS FILTERS TOO --------- Co-authored-by: Plykiya <plykiya@protonmail.com> * Industrial Reagent Grinder Hotfix (#26571) fixed * Give stores the ability to check for owner only (#26573) adds a check if the store belongs to the user * Fix round start crash (causing instant restart) (#26579) * Fix round start crash * Make `TryCreateObjective` more error tolerant * Update engine to v217.1.0 (#26588) * Fix initial infected icon hiding (#26585) * Fix Meta evac shuttle name (#26587) * Make timer ignore client predict setting (#26554) * Make timer ignore client predict setting * making tests run --------- Co-authored-by: wrexbe <wrexbe@protonmail.com> * Make advertise system survive no map inits (#26553) * Make advertise system survive no map inits * Add comment to try prevent future bugs * Update Credits (#26589) Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> * Fix fox spawn on reach (#26584) Co-authored-by: wrexbe <wrexbe@protonmail.com> * Removes SCAF armor (#26566) * removes scaf armor * replace maint loot spawner spot with basic helmet * Update Patrons.yml (#26578) * Automatic changelog update * Make aghost command work on other players using optional argument (#26546) * Translations * Make aghost command work on other players using optional argument * Reviews * Automatic changelog update * Add new component to Make sound on interact (#26523) * Adds new Component: EmitSoundOnInteractUsing * Missed an import * File-scoping * Replace ID check with Prototype check * Moved component and system to shared. Set prediction to true. * Removed impoper imports and changed namespace of component to reflect changed folder. * Following function naming theme * All this code is basically deltanedas's, but it was a learning experience for me * Update Content.Shared/Sound/Components/EmitSoundOnInteractUsingComponent.cs * Update Content.Shared/Sound/Components/EmitSoundOnInteractUsingComponent.cs --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Increase syndi duffelbag storage (#26565) * Increase syndi duffelbag storage * weh * Automatic changelog update * Adds construction/decon graphs for plastic flaps (#26341) * Adds construction/decon graphs for plastic flaps * Dang arbitrage * undo conflict --------- Co-authored-by: Velcroboy <velcroboy333@hotmail.com> * Automatic changelog update * Makes secglasses roundstart (#26487) * makes secglasses roundstart * fix epic fail * fix tests questionmark? * Update Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> --------- Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> * Automatic changelog update * Toilet Upgrade (needs review) (#22133) * Toilet Draft * fixes * toilets now have secret stash to place items in cistern. * fixes * plungers now unblock toilets. * fix sprite * new sprites and fix * fixes * improve seat sprites. * fix * removed visualisersystem changed to genericvisualizers * flush sound for toilets and copyright for toilet sprites. * fix atrributions * fixes * fix datafield flushtime * sprite improvements * fixes * multiple changes * fix * fix * fixes remove vv * moved stash related functions to secret stash system from toilet. * fix * fix * changes for recent review. * fix * fix * Automatic changelog update * Uplink store interface searchable with a searchbar. (#24287) * Can now search the uplink store interface with a searchbar. * Search text updates no longer send server messages. Persists listings locally. * Formatting fixes and tidying. * Added helper method to get localised name and description (or otherwise, entity name and description) of store listing items. * Update Content.Client/Store/Ui/StoreMenu.xaml * Review change; moved localisation helper functions to their own class. * Prevent thread-unsafe behaviour as-per review. * Remove dummy boxcontainer --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> * Automatic changelog update * Improved RCDs (#22799) * Initial radial menu prototyping for the RCD * Radial UI buttons can send messages to the server * Beginning to update RCDSystem * RCD building system in progress * Further updates * Added extra effects, RCDSystem now reads RCD prototype data * Replacing tiles is instant, multiple constructions are allowed, deconstruction is broken * Added extra functionality to RadialContainers plus documentation * Fixed localization of RCD UI strings * Menu opens near cursor, added basic RCD * Avoiding merge conflict * Implemented atomized construction / deconstruction rules * Increased RCD ammo base charges * Moved input context definition to content * Removed obsoleted code * Updates to system * Switch machine and computer frames for electrical cabling * Added construction ghosts * Fixed issue with keybind detection code * Fixed RCD construction ghost mispredications * Code clean up * Updated deconstruction effects * RCDs effects don't rotate * Code clean up * Balancing for ammo counts * Code clean up * Added missing localized strings * More clean up * Made directional window handling more robust * Added documentation to radial menus and made them no longer dependent on Content * Made radial containers more robust * Further robustness to the radial menu * The RCD submenu buttons are only shown when the destination layer has at least one children * Expanded upon deconstructing plus construction balance * Fixed line endings * Updated list of RCD deconstructable entities. Now needs a component to deconstruct instead of a tag * Bug fixes * Revert unnecessary change * Updated RCD strings * Fixed bug * More fixes * Deconstructed tiles/subflooring convert to lattice instead * Fixed failed tests (Linux doesn't like invalid spritespecifer paths) * Fixing merge conflict * Updated airlock assembly * Fixing merge conflict * Fixing merge conflict * More fixing... * Removed erroneous project file change * Fixed string handling issue * Trying to fix merge conflict * Still fixing merge conflicts * Balancing * Hidden RCD construction ghosts when in 'build' mode * Fixing merge conflict * Implemented requested changes (Part 1) * Added more requested changes * Fix for failed test. Removed sussy null suppression * Made requested changes - custom construction ghost system was replaced * Fixing merge conflict * Fixed merge conflict * Fixed bug in RCD construction ghost validation * Fixing merge conflict * Merge conflict fixed * Made required update * Removed lingering RCD deconstruct tag * Fixing merge conflict * Merge conflict fixed * Made requested changes * Bug fixes and balancing * Made string names more consistent * Can no longer stack catwalks * Automatic changelog update * Update submodule to 217.2.0 (#26592) * Southern accent (#26543) * created the AccentComponent and the AccentSystem * word replacement schtuhff * made it a trait fr ongg!!1 * Update Content.Server/Speech/EntitySystems/SouthernAccentSystem.cs --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Prevent storing liquids in equipped buckets (#24412) * Block access to solutions in equipped spillables. * Stop Drink verb appearing if the solution can't be accessed. * Automatic changelog update * Fix 'Hypopen shouldn't display solution examine text' (#26453) * stealthy hypo * ExaminableSolution hand check when in covert implement. ExaminableSolution now has 'hidden' datafield to enable chemical inspection only in hand. * cleaning code * more cleaning * Hidden datafield renamed to HeldOnly * review --------- Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> * Automatic changelog update * Revert Paint (#26593) * Revert "Fix build (#26258)" This reverts commit 6de5fbfafbde700d711a566f6a43f05f7a99e455. * Revert "Spray Paint (Review Ready) (#23003)" This reverts commit e4d5e7f1aebfc37b1bc3453fdb39578f3897b6a1. # Conflicts: # Resources/Prototypes/Entities/Structures/Holographic/projections.yml * Fix: Prevent single-use hyposprays from getting the toggle draw verb (#26595) Prevent single-use hyposprays from getting the toggle draw verb Co-authored-by: Plykiya <plykiya@protonmail.com> * MeleeHitSoundSystem (#25005) * Began work to unscrew melee noises * finished * cleanup * cleanup * Update Content.Server/Weapons/Melee/MeleeWeaponSystem.cs Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com> * _Style * Fix merge --------- Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> * Remove physics comp from VendingMachineWallmount (#25632) * Remove physics comp from VendingMachineWallmount * Fixtures removal --------- Co-authored-by: Jeff <velcroboy333@hotmail.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> * Remake hairflowers (#25475) * Add more lily usage (orange hairflower and flowercrown) * comit 2 * ee * more fixes * w * im stupid * bring poppy in authodrobe * weh --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Automatic changelog update * Injector UI shows TransferAmount change, Spilling liquid changes Injector mode (#26596) * Injector UI shows TransferAmount change, spill changes mode * Update Content.Shared/Fluids/SharedPuddleSystem.Spillable.cs * Update Content.Shared/Fluids/SharedPuddleSystem.Spillable.cs --------- Co-authored-by: Plykiya <plykiya@protonmail.com> Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Update submodule to 217.2.1 (#26599) * disallow unanchoring or opening panels on locked emitters/APEs (#26600) * disallow unanchoring or opening panels on locked emitters/APEs * no locking open panels * oops * needback feedback * Update Content.Shared/Lock/LockSystem.cs * Update Content.Shared/Lock/LockSystem.cs * Update Content.Shared/Lock/LockSystem.cs * sanity --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> * Automatic changelog update * Fix grave digging sound indefinitely playing if dug by aghost. (#26420) Admins bypass doafters. As such, the code that runs on doafter completion is ran before the sound is actually created. This then leads to the sound never being stopped, and as such it would infinitely play. This commit gets around the issue by manually stopping the sound should the doafter fail to start. If we could be sure that the doafter would never fail, then we could just move the call to StartDigging above starting the doafter but this is currently not possible. Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> * Make the buttons on the map ui not squished (#26604) Make the map ui work Co-authored-by: wrexbe <wrexbe@protonmail.com> * Combine flower crown and wreath (#26605) * Combine flower crown and wreath * huh * huuh :trollface: * Automatic changelog update * Add AP damage to throwing knives (#26380) * add * ap * no more stam dmg * Automatic changelog update * cancelable brig timers (#26557) brig timers now cancelable. also some screensystem yakshave * Fix orientation of roller skate sprites (#26627) Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es> * Automatic changelog update * Fix GastTileOverlay sending redundant data (#26623) Fix GastTileOverlay not updating properly * Auto DeAdmin sooner (#26551) Co-authored-by: wrexbe <wrexbe@protonmail.com> * Add briefcase to curadrobe and lawdrobe, and some briefcases cleanup (#26527) * Add briefcase to curadrobe and some briefcases cleanup * also add to lawdrobe * Automatic changelog update * Fix some text overflow bugs in HUD (#26615) * Don't clip text in item status * Fix overflow in examine tooltip --------- Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es> * Adds two milk cartons to the BoozeOMat (#26635) * Automatic changelog update * made the hover text less vague (sorry) (#26630) * blacklisted throwing knifes from pneumatic cannon (#26628) * Fix radio jammer not blocking suit sensors. (#26632) As it turns out, they are not in fact on their own netid. They are actually just on wireless. The way I had tested my previous pr led to this mistake being made. I originally had the radio jammer block wireless as well, but decided to take out under the flase assumption that it suit sensors were actually on their own netid and did not require the ability to block all wireless packets at the last moment. * Fix dirt decals in reach not being cleanable (#26636) made all dirt decals cleanable Co-authored-by: hamurlik <renoDeath@protonmail.com> * Automatic changelog update * Replace drill_hit.ogg and drill_use.ogg with better sounds (#26622) * Replace drill_hit.ogg and drill_use.ogg with better sounds * Fix attribution source for drill_hit.ogg * Update Resources/Audio/Items/attributions.yml Co-authored-by: Kara <lunarautomaton6@gmail.com> * Update Resources/Audio/Items/attributions.yml Co-authored-by: Kara <lunarautomaton6@gmail.com> --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: Kara <lunarautomaton6@gmail.com> * Gave Blast door access permissions (#26606) Added access reader to all blast doors. Added pre configured blast doors for engineering and science. * Gives all wheeled objects low friction (#26601) * gives all wheeled objects friction * adjustments to sum stuff * Automatic changelog update * Combine solution injection systems; Fix embeddable injectors (#26268) * Combine injection systems * Update Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Automatic changelog update * Add ValueList import (#26640) * Change assault borg modules texture (#26502) * Update borg_modules.yml * Fix borg_modules.yml? * Uh --------- Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> * Automatic changelog update * Add Cyborg Emote Sounds (#26594) * Hal 9000's first emote * Add Chime emote & Change variation to 0.05 * Modify Buzz emote * Add Buzz-two emote * modified Horn * add ping emote * add slowclap emote * Convert slowclap.ogg to mono, reflect change in attribution.yml * fix capitalization for all chatMessages && change all catagory to category * remove all traces of slowclap.ogg * forgor one file smh * collating copywrite * spelling mistakes will be the death of me * more spelling mistakes * change yml string to list * Automatic changelog update * Coordinates Disks & Shuttle FTL Travel (#23240) * Adds the CentComm Disk and configures it to work with direct-use shuttles * Added functionality for drone shuttles (i.e. cargo shuttle) * Adds support for pods, and a disk console object for disks to be inserted into. Also sprites. * Added the disk to HoP's locker * Removed leftover logs & comments * Fix for integration test * Apply suggestions from code review (formatting & proper DataField) Co-authored-by: 0x6273 <0x40@keemail.me> * Fix integration test & changes based on code review * Includes Disk Cases to contain Coordinate Disks, which are now CDs instead of Floppy Disks * Check pods & non-evac shuttles for CentCom travel, even in FTL * Import * Remove CentCom travel restrictions & pod disk consoles * Major changes that changes the coordinates disk system to work with salvage expeditions * Missed CC diskcase removal * Fix build * Review suggestions and changes * Major additional changes after merge * Minor tag miss * Integration test fix * review --------- Co-authored-by: 0x6273 <0x40@keemail.me> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> * Automatic changelog update * Add door electronics access configuration menu (#17778) * Add door electronics configuration menu * Use file-scoped namespaces Signed-off-by: c4llv07e <kseandi@gmail.com> * Open door electronics configuration menu only with network configurator Signed-off-by: c4llv07e <kseandi@gmail.com> * Doors will now try to move their AccessReaderComponent to their door electronics when the map is initialized Signed-off-by: c4llv07e <kseandi@gmail.com> * Make the access list in the id card computer a separate control Signed-off-by: c4llv07e <kseandi@gmail.com> * Fix merge conflict Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove DoorElectronics tag Signed-off-by: c4llv07e <kseandi@gmail.com> * Integrate doors with #17927 Signed-off-by: c4llv07e <kseandi@gmail.com> * Move door electornics ui stuff to the right place Signed-off-by: c4llv07e <kseandi@gmail.com> * Some review fixes Signed-off-by: c4llv07e <kseandi@gmail.com> * More fixes Signed-off-by: c4llv07e <kseandi@gmail.com> * review fix Signed-off-by: c4llv07e <kseandi@gmail.com> * move all accesses from airlock prototypes to door electronics Signed-off-by: c4llv07e <kseandi@gmail.com> * rework door electronics config access list Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove Linq from the door electronics user interface * [WIP] Add EntityWhitelist to the activatable ui component Signed-off-by: c4llv07e <kseandi@gmail.com> * Better interaction system Signed-off-by: c4llv07e <kseandi@gmail.com> * Refactor Signed-off-by: c4llv07e <kseandi@gmail.com> * Fix some door electronics not working without AccessReaderComponent Signed-off-by: c4llv07e <kseandi@gmail.com> * Move AccessReaderComponent update code to the AccessReaderSystem Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove unnecesary newlines in the door access prototypes Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove unused variables in access level control Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove unnecessary method from the door electronics configuration menu Signed-off-by: c4llv07e <kseandi@gmail.com> * [WIP] change access type from string to ProtoId<AccessLevelPrototype> Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove unused methods Signed-off-by: c4llv07e <kseandi@gmail.com> * Newline fix Signed-off-by: c4llv07e <kseandi@gmail.com> * Restored to a functional state Signed-off-by: c4llv07e <kseandi@gmail.com> * Fix access configurator not working with door electronics AccessReaderComponent Signed-off-by: c4llv07e <kseandi@gmail.com> * Replace all string access fields with ProtoId Signed-off-by: c4llv07e <kseandi@gmail.com> * move access level control initialization into Populate method Signed-off-by: c4llv07e <kseandi@gmail.com> * Review --------- Signed-off-by: c4llv07e <kseandi@gmail.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> * Automatic changelog update * scoopable ash and foam, solution transfer prediction (#25832) * move SolutionTransfer to shared and predict as much as possible * fully move OpenableSystem to shared now that SolutionTransfer is * fix imports for everything * doc for solution transfer system * trolling * add scoopable system * make ash and foam scoopable * untroll * untroll real * make clickable it work * troll * the scooping room --------- Co-authored-by: deltanedas <@deltanedas:kde.org> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> * Replace the teleportation logic on the SCRAM implant! (#26429) * Replace the teleportation logic on the SCRAM implant! Now instead of just trying to pick a random tile in range 20 times, the scram teleportation logic now: - Gets a list of grids in range - Until a suitable tile is picked it picks a random grid - From that grid it picks a random tile. - If the tile is suitable, then it is set as the target and the user will be teleported there. - Grids and tiles are randomly picked as outlined above until a valid tile is found, or all valid grids and tiles are exhausted. - Should no suitable tile be found then they get teleported to the same position they are at. Effectively not teleporting them. * Actually make the defaults sane which I forgor in the last commit * Extract tile section to its own function. Bias selection for current grid. Use proper coords for box. * Address reviews as much as possible * Address reviews * Refactored AdvertiseComponent (#26598) * Made it better * ok * alright --------- Co-authored-by: wrexbe <wrexbe@protonmail.com> * Bartender "Essentials" (#25367) * drinks round 1 saving my progress before my hard drive explodes * test 2 please work * name fixes whoops * Update drinks.yml * various fixes am dumb * add sol dry to vends more fixes and changes, yippee! * more fixes & ingame testing shrimple tests * last fixes :trollface: should be ready for pr now * Update soda.yml sate thirst * Automatic changelog update * Add ERT Chaplain (#25956) * ERT Chaplain * Make BibleUser * It was not intended * reword my poor words * 1984 a comment that I decided was unnecessary. * Update Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Changes in chemicals page in guidebook (#25831) * Added pages to chemical categories The chemical categories have their own page now. Added the "Chemical Tabs" in /ServerInfo/Guidebook. Moved the Chemicals code from shiftsandjobs.yml to its own .yml file which is "chemicals.yml". * Update guides.ftl * Update chemicals.yml Changed the guide entry's ID for the medical tab from Medicine to Medicinal. Hope this works... * Update Resources/ServerInfo/Guidebook/Chemical Tabs/Biological.xml Co-authored-by: exincore <me@exin.xyz> * Update Resources/ServerInfo/Guidebook/Chemical Tabs/Foods.xml Co-authored-by: exincore <me@exin.xyz> * Update Resources/ServerInfo/Guidebook/Chemical Tabs/Elements.xml Co-authored-by: exincore <me@exin.xyz> * Update Resources/ServerInfo/Guidebook/Chemical Tabs/Narcotics.xml Co-authored-by: exincore <me@exin.xyz> * Update Resources/ServerInfo/Guidebook/Chemical Tabs/Toxins.xml Co-authored-by: exincore <me@exin.xyz> * Fixed a few errors and stuff! A few typos have been fixed thanks to exincore. Added dedicated .xml files to be used for the dedicated category pages (Medicinal and Botanical pages). Made it so it doesn't use any duplicated IDs anymore. If there's more problems, please do tell so I can fix it! * Update settings.json * Fix? --------- Co-authored-by: exincore <me@exin.xyz> * Automatic changelog update * Anomalies behaviours (#24683) * Added new anomaly particle * Add basic anomaly behaviour * +2 parametres * add functional to new particle * add components to behaviours * big content * add shuffle, moved thing to server * clean up * fixes * random pick redo * bonjour behavioUr * fix AJCM * fix * add some new behaviours * power modifier behaviour * rmeove timer * new event for update ui fix * refactor! * fixes * enum * Fix mapinit * Minor touches --------- Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> * Automatic changelog update * Fix clipping/overlap in lathe machine UIs (#26646) * Add scrollbars to lathe material list when necessary * Fix bug where shrinking window would cause elements to overlap --------- Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es> * Added chat window transparency slider to options (#24990) * Adds a new slider to the misc tab in options that lets the player set chat window transparency * Tweaked variable names * Fixed order to match UI * Renamed set chat window transparency function * Changed and refactored to opacity instead of transparency * Remove unnecessary int to float conversions Slider used to be 0-100 while the CCVar was 0.0-1.0f. This is confusing and was only used for rounding to 2 decimal points. * Round the value to two decimal points * Remove rounding for now * Rename * Unhardcode chat color by moving to stylesheet * Fix indent * Make opacity slider only change opacity --------- Co-authored-by: Your Name <you@example.com> Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> * Automatic changelog update * Infinity books (#25840) * setup text data * roundstart reshuffling keywords with gibberish words * saved data categorized * add book with hints * start redrawing books * +4 book design * +books +random visual upgrade * finish first file * finish lore file * finish with books.rsi now authorbooks.rsi... * aurora! and some fix * nuke author books * speelbuke update * finish respriting work * fix scientist guide visual * setup datasets * setup stupid funny random story * restore author books, upgrade hint generation * add variety to story generator * add learning system * minor textgen edit * file restruct, hint count variation * more restruct * more renaming add basis learning system logic. Spears locked for special book for test. * nuke all systems, for splitting PR gods * typo fix * update migration with deleted books * add random story books to maint * Update construction-system.ftl * Update Resources/Prototypes/Datasets/Names/books.yml Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * Update Resources/Prototypes/Datasets/Names/books.yml Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * Update Resources/Prototypes/Datasets/Names/books.yml Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * Update Resources/Prototypes/Datasets/Names/books.yml Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * Update Resources/Prototypes/Datasets/Names/books.yml Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * Update Resources/Prototypes/Datasets/Names/books.yml Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * Update Resources/Prototypes/Datasets/Names/books.yml Co-authored-by: Hrosts <35345601+Hrosts@users.noreply.github.com> * Update Resources/Prototypes/Datasets/Names/books.yml Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * Update Resources/Prototypes/Datasets/Names/books.yml Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * Update Resources/Prototypes/Datasets/Names/books.yml Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * Update Resources/Prototypes/Datasets/Names/books.yml Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * Update Resources/Prototypes/Datasets/Names/books.yml Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * typo fix * interchangeably * final * Update Resources/Prototypes/Datasets/Names/books.yml Co-authored-by: Hrosts <35345601+Hrosts@users.noreply.github.com> * "." * Update Content.Server/Paper/PaperRandomStorySystem.cs Co-authored-by: Hrosts <35345601+Hrosts@users.noreply.github.com> * Ubazer fix * inadequate * localized * Update meta.json * fuck merge conflicts * fix jani book --------- Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> Co-authored-by: Hrosts <35345601+Hrosts@users.noreply.github.com> * Automatic changelog update * Resprite ambuzol plus pills (#26651) * Automatic changelog update * Fixed air injector visuals (#26654) * Make cyborgs hands explosion proof. (#26515) * Make the advanced treatment modules beakers explosion-proof. * undo changes * Epic rename fail * Explosion recursion data field * Logic for data field * Automatic changelog update * Automatic changelog update * Make typing indicator shaded (#26678) * Automatic changelog update * Validate wire layout prototypes and remove invalid WiresComponents (#26682) Validate wire layout prototypes; delete invalid wirescomponents. * Increase time inbetween anomaly pulses (#26677) nerf anomaly pulse delays * Automatic changelog update * Fix for items dropped being rotated to world north (#26662) * Fix rotation of dropped items * combined world position rotation function for dumpable * scuffed implementation? * less scuffed? * even less scuffed... I guess * capital D --------- Co-authored-by: Plykiya <plykiya@protonmail.com> * Automatic changelog update * fix double interaction popup (#26684) change popentity to popupclient * disable foam scooping (#26686) Co-authored-by: deltanedas <@deltanedas:kde.org> * Automatic changelog update * Little disk printer sprite tweaks (#26711) * Little disk printer sprite tweaks * ill change this aswell * fixed white_box.png (#26714) * Delete Resources/Textures/Decals/bricktile.rsi/white_box.png * Readded fixed version one pixel change * New lobby art: TerminalStation (#26505) woop woop * Automatic changelog update * Unidentified corpses respect gender pronouns (#26715) fix: LGBT erasure /j * Things that can't go in disposals now don't "Miss" (#26716) * Moved is canInsert check to before miss check * Update Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Clean up YAML issues in animals.yml (#26696) * Cleaned up YAML issues in animals.yml * Cleaned up TimedSpawnerComponent * fix health analyzer crash (#26700) Co-authored-by: deltanedas <@deltanedas:kde.org> * Automatic changelog update * Make the station start with random broken wiring (#26695) Random wire cutting on round start * Fix turned off thrusters consume power (#26690) * Mail Unit Fix (whitelist) (#26688) Fix Mail Unit * Automatic changelog update * OOC Patron Color Toggle (#26653) * Adds the option for you to toggle your OOC Patron color visibility to yourself and others. * Makes the button magically disappear if you arent a patron * Automatic changelog update * Fix random clothing slots being able to hide character's nose and hair (#26708) Fix bug and formatting * Automatic changelog update * Make Zombie, Initial Infected fix (#26665) Make zombie fix * Suit Sensors No Longer Use a Hardcoded 'Total Health' (#26658) * Suit sensors now know the 'total health' of an entity * Missed the constructor :pensive: * Stop mop buckets from spilling when you push them (#26706) * Automatic changelog update * Robotists technology icon fix (#26723) fix Co-authored-by: GeneralGaws <limonmessi@mail.ru> * Make ducks more viable as an alternative to chickens. (#26729) Quick tweak to make ducks on par with chickens at cargo * Automatic changelog update * Make the nutribrick one bite smaller (#26719) Update snacks.yml * Task/fix nightvision huds (#26726) * StatusIcon: add field to set if icon should be rendered with shading * set/unset shader based on icon field * set new field to true for hud icons * re-shade health bars * Automatic changelog update * Rework Identifier Overrides to prevent showing Law Priority (#26680) Does-The-Fix Co-authored-by: Mephisto72 <Mephisto.Respectator@proton.me> * Make practice projectiles consistent in damage (#26731) * Make practice weapon damage consistent to 1 * Add book reference to description * Automatic changelog update * fix mopbucket water level (#26740) * Damage popup type can now be changed with a left click if allowed via component boolean. (#26734) * Update DamagePopupSystem.cs * Update DamagePopupSystem.cs * Add ability to allow or deny type change via component bool * Automatic changelog update * CCVars.cs: Minor inconsistency fixes. (#26744) Update CCVars.cs * Fixes one file format inconsistency. * Adds missing </summary> closing tag. * Make baseball bat crafting require a slicing tool (#26742) Make baseball bat crafting harder * make fulton recipe faster and require cloth (#26747) Co-authored-by: deltanedas <@deltanedas:kde.org> * Automatic changelog update * -fixed Broadcast button never enabling (#26750) * Automatic changelog update * Let Mindshields be effected by statusIcon shading (#26754) Phone Webedit Ops Original PR author forgot about mindshields for making status icons shaded. This can be done with other antag icons as well, I remember people mentioning revs being able to see each other in the dark was lame. * Automatic changelog update * Dionae now bleed sap, and this can be used to make syrup. (#25748) * SapAndSyrup * centrifug * morewatervapor * whyisitnotpushing * nymphs * lessrealmorefun * Automatic changelog update * Alerts crash fix (#26602) - If the client tries to call ShowAlert while applying gamestates (e.g. initialising an entity) then this will cause problems. I need to double-check the initial PR before I'd be comfortable with this being merged. * made thin firelocks constructable/deconstructable (#26745) * Automatic changelog update * Fire sprite change for mice (#26758) * Add new fire sprite for mice that fits them better * Add the sprite change to rats as well * Moffroach and hamsters now also have more fitting fire sprites * made the meta.json easier to read * Automatic changelog update * Change speed threshold for barefeet walking on glass shards and D4 (#26763) Allow walking over glass shards and D4 Co-authored-by: Plykiya <plykiya@protonmail.com> * Automatic changelog update --------- Signed-off-by: c4llv07e <kseandi@gmail.com> Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com> Co-authored-by: Plykiya <58439124+Plykiya@users.noreply.github.com> Co-authored-by: Plykiya <plykiya@protonmail.com> Co-authored-by: Boaz1111 <149967078+Boaz1111@users.noreply.github.com> Co-authored-by: keronshb <54602815+keronshb@users.noreply.github.com> Co-authored-by: Wrexbe (Josh) <81056464+wrexbe@users.noreply.github.com> Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Co-authored-by: wrexbe <wrexbe@protonmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com> Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com> Co-authored-by: Simon <63975668+Simyon264@users.noreply.github.com> Co-authored-by: blueDev2 <89804215+blueDev2@users.noreply.github.com> Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com> Co-authored-by: Velcroboy <velcroboy333@hotmail.com> Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Co-authored-by: brainfood1183 <113240905+brainfood1183@users.noreply.github.com> Co-authored-by: J. Brown <DrMelon@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> Co-authored-by: UBlueberry <161545003+UBlueberry@users.noreply.github.com> Co-authored-by: Tayrtahn <tayrtahn@gmail.com> Co-authored-by: drteaspoon420 <87363733+drteaspoon420@users.noreply.github.com> Co-authored-by: Bixkitts <72874643+Bixkitts@users.noreply.github.com> Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Co-authored-by: Ubaser <134914314+UbaserB@users.noreply.github.com> Co-authored-by: avery <51971268+graevy@users.noreply.github.com> Co-authored-by: eoineoineoin <github@eoinrul.es> Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es> Co-authored-by: RiceMar1244 <138547931+RiceMar1244@users.noreply.github.com> Co-authored-by: Zealith-Gamer <61980908+Zealith-Gamer@users.noreply.github.com> Co-authored-by: hamurlik <75280571+hamurlik@users.noreply.github.com> Co-authored-by: hamurlik <renoDeath@protonmail.com> Co-authored-by: no <165581243+pissdemon@users.noreply.github.com> Co-authored-by: Kara <lunarautomaton6@gmail.com> Co-authored-by: SoulFN <164462467+SoulFN@users.noreply.github.com> Co-authored-by: Keer-Sar <144283718+Keer-Sar@users.noreply.github.com> Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> Co-authored-by: 0x6273 <0x40@keemail.me> Co-authored-by: c4llv07e <38111072+c4llv07e@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: Firewatch <54725557+musicmanvr@users.noreply.github.com> Co-authored-by: IProduceWidgets <107586145+IProduceWidgets@users.noreply.github.com> Co-authored-by: f0x-n3rd <150924715+f0x-n3rd@users.noreply.github.com> Co-authored-by: exincore <me@exin.xyz> Co-authored-by: Sk1tch <ben.peter.smith@gmail.com> Co-authored-by: Your Name <you@example.com> Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Co-authored-by: Hrosts <35345601+Hrosts@users.noreply.github.com> Co-authored-by: osjarw <62134478+osjarw@users.noreply.github.com> Co-authored-by: lunarcomets <140772713+lunarcomets@users.noreply.github.com> Co-authored-by: ThatOneGoblin25 <145570657+ThatOneGoblin25@users.noreply.github.com> Co-authored-by: Terraspark4941 <terraspark4941@gmail.com> Co-authored-by: Brandon Hu <103440971+Brandon-Huu@users.noreply.github.com> Co-authored-by: beck-thompson <107373427+beck-thompson@users.noreply.github.com> Co-authored-by: Aexxie <codyfox.077@gmail.com> Co-authored-by: DinoWattz <116862698+DinoWattz@users.noreply.github.com> Co-authored-by: Pspritechologist <81725545+Pspritechologist@users.noreply.github.com> Co-authored-by: Vasilis <vasilis@pikachu.systems> Co-authored-by: GeneralGaws <122978178+GeneralGaws@users.noreply.github.com> Co-authored-by: GeneralGaws <limonmessi@mail.ru> Co-authored-by: Dae <60460608+ZeroDayDaemon@users.noreply.github.com> Co-authored-by: potato1234_x <79580518+potato1234x@users.noreply.github.com> Co-authored-by: PrPleGoo <PrPleGoo@users.noreply.github.com> Co-authored-by: Mephisto72 <66994453+Mephisto72@users.noreply.github.com> Co-authored-by: Mephisto72 <Mephisto.Respectator@proton.me> Co-authored-by: TsjipTsjip <19798667+TsjipTsjip@users.noreply.github.com> Co-authored-by: Verm <32827189+Vermidia@users.noreply.github.com> Co-authored-by: superjj18 <gagnonjake@gmail.com> Co-authored-by: Golinth <amh2023@gmail.com> Co-authored-by: Alzore <140123969+Blackern5000@users.noreply.github.com> Co-authored-by: BITTERLYNX <166083655+PaigeMaeForrest@users.noreply.github.com>
2024-04-06 13:13:22 +03:00
_popupSystem.PopupClient(message, origin, origin);
}
return inRange;
}
public bool InRangeUnobstructed(
MapCoordinates origin,
EntityUid target,
float range = InteractionRange,
CollisionGroup collisionMask = InRangeUnobstructedMask,
Ignored? predicate = null)
{
var transform = Transform(target);
var (position, rotation) = transform.GetWorldPositionRotation();
var mapPos = new MapCoordinates(position, transform.MapID);
var combinedPredicate = GetPredicate(origin, target, mapPos, rotation, collisionMask, predicate);
return InRangeUnobstructed(origin, mapPos, range, collisionMask, combinedPredicate);
}
/// <summary>
/// Gets the entities to ignore for an unobstructed raycast
/// </summary>
/// <example>
/// if the target entity is a wallmount we ignore all other entities on the tile.
/// </example>
private Ignored GetPredicate(
MapCoordinates origin,
EntityUid target,
MapCoordinates targetCoords,
Angle targetRotation,
CollisionGroup collisionMask,
Ignored? predicate = null)
{
HashSet<EntityUid> ignored = new();
2022-07-27 03:53:47 -07:00
if (HasComp<ItemComponent>(target) && TryComp(target, out PhysicsComponent? physics) && physics.CanCollide)
{
// If the target is an item, we ignore any colliding entities. Currently done so that if items get stuck
// inside of walls, users can still pick them up.
ignored.UnionWith(_sharedBroadphaseSystem.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics));
}
else if (TryComp(target, out WallMountComponent? wallMount))
{
// wall-mount exemptions may be restricted to a specific angle range.da
bool ignoreAnchored;
if (wallMount.Arc >= Math.Tau)
ignoreAnchored = true;
else
{
var angle = Angle.FromWorldVec(origin.Position - targetCoords.Position);
var angleDelta = (wallMount.Direction + targetRotation - angle).Reduced().FlipPositive();
ignoreAnchored = angleDelta < wallMount.Arc / 2 || Math.Tau - angleDelta < wallMount.Arc / 2;
}
if (ignoreAnchored && _mapManager.TryFindGridAt(targetCoords, out _, out var grid))
ignored.UnionWith(grid.GetAnchoredEntities(targetCoords));
}
Ignored combinedPredicate = e =>
{
return e == target
|| (predicate?.Invoke(e) ?? false)
|| ignored.Contains(e);
};
return combinedPredicate;
}
/// <summary>
/// Checks that an entity and a set of grid coordinates are within a certain
/// distance without any entity that matches the collision mask
/// obstructing them.
/// If the <paramref name="range"/> is zero or negative,
/// this method will only check if nothing obstructs the entity and component.
/// </summary>
/// <param name="origin">The entity to use.</param>
/// <param name="other">The grid coordinates to use.</param>
/// <param name="range">
/// Maximum distance between the two entity and set of grid coordinates.
/// </param>
/// <param name="collisionMask">The mask to check for collisions.</param>
/// <param name="predicate">
/// A predicate to check whether to ignore an entity or not.
/// If it returns true, it will be ignored.
/// </param>
/// <param name="popup">
/// Whether or not to popup a feedback message on the origin entity for
/// it to see.
/// </param>
/// <returns>
/// True if the two points are within a given range without being obstructed.
/// </returns>
public bool InRangeUnobstructed(
2021-12-04 12:35:33 +01:00
EntityUid origin,
EntityCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = InRangeUnobstructedMask,
Ignored? predicate = null,
bool popup = false)
{
return InRangeUnobstructed(origin, other.ToMap(EntityManager, _transform), range, collisionMask, predicate, popup);
}
/// <summary>
/// Checks that an entity and a set of map coordinates are within a certain
/// distance without any entity that matches the collision mask
/// obstructing them.
/// If the <paramref name="range"/> is zero or negative,
/// this method will only check if nothing obstructs the entity and component.
/// </summary>
/// <param name="origin">The entity to use.</param>
/// <param name="other">The map coordinates to use.</param>
/// <param name="range">
/// Maximum distance between the two entity and set of map coordinates.
/// </param>
/// <param name="collisionMask">The mask to check for collisions.</param>
/// <param name="predicate">
/// A predicate to check whether to ignore an entity or not.
/// If it returns true, it will be ignored.
/// </param>
/// <param name="popup">
/// Whether or not to popup a feedback message on the origin entity for
/// it to see.
/// </param>
/// <returns>
/// True if the two points are within a given range without being obstructed.
/// </returns>
public bool InRangeUnobstructed(
2021-12-04 12:35:33 +01:00
EntityUid origin,
MapCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = InRangeUnobstructedMask,
Ignored? predicate = null,
bool popup = false)
{
Ignored combinedPredicate = e => e == origin || (predicate?.Invoke(e) ?? false);
var originPosition = Transform(origin).MapPosition;
var inRange = InRangeUnobstructed(originPosition, other, range, collisionMask, combinedPredicate, ShouldCheckAccess(origin));
2022-03-12 12:53:42 +13:00
if (!inRange && popup && _gameTiming.IsFirstTimePredicted)
{
var message = Loc.GetString("interaction-system-user-interaction-cannot-reach");
_popupSystem.PopupEntity(message, origin, origin);
}
return inRange;
}
public bool RangedInteractDoBefore(
2021-12-04 12:35:33 +01:00
EntityUid user,
EntityUid used,
EntityUid? target,
EntityCoordinates clickLocation,
bool canReach)
{
var ev = new BeforeRangedInteractEvent(user, used, target, clickLocation, canReach);
RaiseLocalEvent(used, ev);
// We contact the USED entity, but not the target.
DoContactInteraction(user, used, ev);
return ev.Handled;
}
/// <summary>
/// Uses a item/object on an entity
/// Finds components with the InteractUsing interface and calls their function
/// NOTE: Does not have an InRangeUnobstructed check
/// </summary>
public void InteractUsing(
EntityUid user,
EntityUid used,
EntityUid target,
EntityCoordinates clickLocation,
bool checkCanInteract = true,
bool checkCanUse = true)
{
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, target))
return;
if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user))
return;
if (RangedInteractDoBefore(user, used, target, clickLocation, true))
return;
// all interactions should only happen when in range / unobstructed, so no range check is needed
var interactUsingEvent = new InteractUsingEvent(user, used, target, clickLocation);
RaiseLocalEvent(target, interactUsingEvent, true);
DoContactInteraction(user, used, interactUsingEvent);
DoContactInteraction(user, target, interactUsingEvent);
DoContactInteraction(used, target, interactUsingEvent);
if (interactUsingEvent.Handled)
return;
InteractDoAfter(user, used, target, clickLocation, canReach: true);
}
/// <summary>
/// Used when clicking on an entity resulted in no other interaction. Used for low-priority interactions.
/// </summary>
public void InteractDoAfter(EntityUid user, EntityUid used, EntityUid? target, EntityCoordinates clickLocation, bool canReach)
{
if (target is {Valid: false})
target = null;
var afterInteractEvent = new AfterInteractEvent(user, used, target, clickLocation, canReach);
RaiseLocalEvent(used, afterInteractEvent);
DoContactInteraction(user, used, afterInteractEvent);
if (canReach)
{
DoContactInteraction(user, target, afterInteractEvent);
DoContactInteraction(used, target, afterInteractEvent);
}
if (afterInteractEvent.Handled)
return;
if (target == null)
return;
var afterInteractUsingEvent = new AfterInteractUsingEvent(user, used, target, clickLocation, canReach);
RaiseLocalEvent(target.Value, afterInteractUsingEvent);
DoContactInteraction(user, used, afterInteractUsingEvent);
if (canReach)
{
DoContactInteraction(user, target, afterInteractUsingEvent);
DoContactInteraction(used, target, afterInteractUsingEvent);
}
}
#region ActivateItemInWorld
2022-03-09 20:12:17 +13:00
private bool HandleActivateItemInWorld(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
if (!ValidateClientInput(session, coords, uid, out var user))
{
Log.Info($"ActivateItemInWorld input validation failed");
2022-03-09 20:12:17 +13:00
return false;
}
if (Deleted(uid))
return false;
InteractionActivate(user.Value, uid, checkAccess: ShouldCheckAccess(user.Value));
2022-03-09 20:12:17 +13:00
return false;
}
/// <summary>
/// Raises <see cref="ActivateInWorldEvent"/> events and activates the IActivate behavior of an object.
/// </summary>
/// <remarks>
/// Does not check the can-use action blocker. In activations interacts can target entities outside of the users
/// hands.
/// </remarks>
public bool InteractionActivate(
EntityUid user,
EntityUid used,
bool checkCanInteract = true,
bool checkUseDelay = true,
bool checkAccess = true)
{
UseDelayComponent? delayComponent = null;
if (checkUseDelay
&& TryComp(used, out delayComponent)
2024-01-03 21:33:09 -04:00
&& _useDelay.IsDelayed((used, delayComponent)))
return false;
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used))
return false;
2022-03-12 12:53:42 +13:00
if (checkAccess && !InRangeUnobstructed(user, used))
return false;
// 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 (checkAccess && !_containerSystem.IsInSameOrParentContainer(user, used) && !CanAccessViaStorage(user, used))
return false;
// Does the user have hands?
if (!HasComp<HandsComponent>(user))
return false;
var activateMsg = new ActivateInWorldEvent(user, used);
RaiseLocalEvent(used, activateMsg, true);
if (!activateMsg.Handled)
return false;
DoContactInteraction(user, used, activateMsg);
2024-01-03 21:33:09 -04:00
if (delayComponent != null)
_useDelay.TryResetDelay((used, delayComponent));
2023-01-20 10:05:05 -06:00
if (!activateMsg.WasLogged)
_adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}");
return true;
}
#endregion
#region Hands
#region Use
/// <summary>
/// Raises UseInHandEvents and activates the IUse behaviors of an entity
/// Does not check accessibility or range, for obvious reasons
/// </summary>
/// <returns>True if the interaction was handled. False otherwise</returns>
public bool UseInHandInteraction(
EntityUid user,
EntityUid used,
bool checkCanUse = true,
bool checkCanInteract = true,
bool checkUseDelay = true)
{
UseDelayComponent? delayComponent = null;
if (checkUseDelay
&& TryComp(used, out delayComponent)
2024-01-03 21:33:09 -04:00
&& _useDelay.IsDelayed((used, delayComponent)))
2022-01-06 14:51:34 +13:00
return true; // if the item is on cooldown, we consider this handled.
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used))
return false;
if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user))
return false;
2022-03-13 01:33:23 +13:00
var useMsg = new UseInHandEvent(user);
RaiseLocalEvent(used, useMsg, true);
if (useMsg.Handled)
2022-01-06 14:51:34 +13:00
{
DoContactInteraction(user, used, useMsg);
2024-01-03 21:33:09 -04:00
if (delayComponent != null && useMsg.ApplyDelay)
_useDelay.TryResetDelay((used, delayComponent));
return true;
2022-01-06 14:51:34 +13:00
}
// else, default to activating the item
return InteractionActivate(user, used, false, false, false);
}
/// <summary>
/// Alternative interactions on an entity.
/// </summary>
/// <remarks>
/// Uses the context menu verb list, and acts out the highest priority alternative interaction verb.
/// </remarks>
/// <returns>True if the interaction was handled, false otherwise.</returns>
public bool AltInteract(EntityUid user, EntityUid target)
{
// Get list of alt-interact verbs
var verbs = _verbSystem.GetLocalVerbs(target, user, typeof(AlternativeVerb));
if (!verbs.Any())
return false;
_verbSystem.ExecuteVerb(verbs.First(), user, target);
return true;
}
#endregion
2021-12-04 12:35:33 +01:00
public void DroppedInteraction(EntityUid user, EntityUid item)
{
2022-03-13 21:47:28 +13:00
var dropMsg = new DroppedEvent(user);
RaiseLocalEvent(item, dropMsg, true);
if (dropMsg.Handled)
_adminLogger.Add(LogType.Drop, LogImpact.Low, $"{ToPrettyString(user):user} dropped {ToPrettyString(item):entity}");
// If the dropper is rotated then use their targetrelativerotation as the drop rotation
var rotation = Angle.Zero;
if (TryComp<InputMoverComponent>(user, out var mover))
{
rotation = mover.TargetRelativeRotation;
}
Transform(item).LocalRotation = rotation;
}
#endregion
/// <summary>
/// If a target is in range, but not in the same container as the user, it may be inside of a backpack. This
/// checks if the user can access the item in these situations.
/// </summary>
public abstract bool CanAccessViaStorage(EntityUid user, EntityUid target);
/// <summary>
/// Checks whether an entity currently equipped by another player is accessible to some user. This shouldn't
/// be used as a general interaction check, as these kinda of interactions should generally trigger a
/// do-after and a warning for the other player.
/// </summary>
public bool CanAccessEquipment(EntityUid user, EntityUid target)
{
if (Deleted(target))
return false;
if (!_containerSystem.TryGetContainingContainer(target, out var container))
return false;
var wearer = container.Owner;
if (!_inventory.TryGetSlot(wearer, container.ID, out var slotDef))
return false;
if (wearer == user)
return true;
if (slotDef.StripHidden)
return false;
return InRangeUnobstructed(user, wearer) && _containerSystem.IsInSameOrParentContainer(user, wearer);
}
2021-12-16 23:42:02 +13:00
protected bool ValidateClientInput(ICommonSession? session, EntityCoordinates coords,
EntityUid uid, [NotNullWhen(true)] out EntityUid? userEntity)
{
userEntity = null;
if (!coords.IsValid(EntityManager))
{
Log.Info($"Invalid Coordinates: client={session}, coords={coords}");
2021-12-16 23:42:02 +13:00
return false;
}
if (IsClientSide(uid))
2021-12-16 23:42:02 +13:00
{
Log.Warning($"Client sent interaction with client-side entity. Session={session}, Uid={uid}");
2021-12-16 23:42:02 +13:00
return false;
}
userEntity = session?.AttachedEntity;
if (userEntity == null || !userEntity.Value.Valid)
{
Log.Warning($"Client sent interaction with no attached entity. Session={session}");
2021-12-16 23:42:02 +13:00
return false;
}
if (!Exists(userEntity))
{
Log.Warning($"Client attempted interaction with a non-existent attached entity. Session={session}, entity={userEntity}");
return false;
}
2021-12-16 23:42:02 +13:00
return true;
}
/// <summary>
/// Simple convenience function to raise contact events (disease, forensics, etc).
/// </summary>
public void DoContactInteraction(EntityUid uidA, EntityUid? uidB, HandledEntityEventArgs? args = null)
{
if (uidB == null || args?.Handled == false)
return;
// Entities may no longer exist (banana was eaten, or human was exploded)?
if (!Exists(uidA) || !Exists(uidB))
return;
if (Paused(uidA) || Paused(uidB.Value))
return;
RaiseLocalEvent(uidA, new ContactInteractionEvent(uidB.Value));
RaiseLocalEvent(uidB.Value, new ContactInteractionEvent(uidA));
}
}
/// <summary>
/// Raised when a player attempts to activate an item in an inventory slot or hand slot
/// </summary>
[Serializable, NetSerializable]
public sealed class InteractInventorySlotEvent : EntityEventArgs
{
/// <summary>
/// Entity that was interacted with.
/// </summary>
public NetEntity ItemUid { get; }
/// <summary>
/// Whether the interaction used the alt-modifier to trigger alternative interactions.
/// </summary>
public bool AltInteract { get; }
public InteractInventorySlotEvent(NetEntity itemUid, bool altInteract = false)
{
ItemUid = itemUid;
AltInteract = altInteract;
}
}
/// <summary>
/// Raised directed by-ref on an item to determine if hand interactions should go through.
/// Defaults to allowing hand interactions to go through. Cancel to force the item to be attacked instead.
/// </summary>
/// <param name="Cancelled">Whether the hand interaction should be cancelled.</param>
[ByRefEvent]
public record struct CombatModeShouldHandInteractEvent(bool Cancelled = false);
}