Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Content.Shared/InteractionVerbs/InteractionArgs.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Hands.Components;
using Content.Shared.Verbs;
using Robust.Shared.Serialization;

Expand All @@ -8,7 +9,7 @@ public sealed partial class InteractionArgs
{
public EntityUid User, Target;
public EntityUid? Used;
public bool CanAccess, CanInteract;
public bool CanAccess, CanInteract, HasHands;

/// <summary>
/// A float value between 0 and positive infinity that indicates how much stronger the user
Expand All @@ -27,19 +28,20 @@ public sealed partial class InteractionArgs
public Dictionary<string, object> Blackboard => _blackboardField ??= new(3);
private Dictionary<string, object>? _blackboardField; // null by default, allocated lazily (only if actually needed)

public InteractionArgs(EntityUid user, EntityUid target, EntityUid? used, bool canAccess, bool canInteract, float? contestAdvantage)
public InteractionArgs(EntityUid user, EntityUid target, EntityUid? used, bool canAccess, bool canInteract, bool hasHands, float? contestAdvantage)
{
User = user;
Target = target;
Used = used;
CanAccess = canAccess;
CanInteract = canInteract;
HasHands = hasHands;
ContestAdvantage = contestAdvantage;
}

public InteractionArgs(InteractionArgs other) : this(other.User, other.Target, other.Used, other.CanAccess, other.CanInteract, other.ContestAdvantage) {}
public InteractionArgs(InteractionArgs other) : this(other.User, other.Target, other.Used, other.CanAccess, other.CanInteract, other.HasHands, other.ContestAdvantage) {}

public static InteractionArgs From<T>(GetVerbsEvent<T> ev) where T : Verb => new(ev.User, ev.Target, ev.Using, ev.CanAccess, ev.CanInteract, null);
public static InteractionArgs From<T>(GetVerbsEvent<T> ev) where T : Verb => new(ev.User, ev.Target, ev.Using, ev.CanAccess, ev.CanInteract, ev.Hands is not null, null);

/// <summary>
/// Tries to get a value from the blackboard as an instance of a specific type.
Expand Down
112 changes: 79 additions & 33 deletions Content.Shared/InteractionVerbs/SharedInteractionVerbsSystem.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.ActionBlocker;
using Content.Shared.Contests;
using Content.Shared.DoAfter;
using Content.Shared.Ghost;
using Content.Shared.Interaction;
using Content.Shared.InteractionVerbs.Events;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
Expand All @@ -22,9 +26,12 @@ public abstract class SharedInteractionVerbsSystem : EntitySystem
private readonly InteractionAction.VerbDependencies _verbDependencies = new();
private List<InteractionVerbPrototype> _globalPrototypes = default!;

[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfters = default!;
[Dependency] private readonly SharedContainerSystem _containers = default!;
[Dependency] private readonly ContestsSystem _contests = default!;
[Dependency] private readonly SharedInteractionSystem _interactions = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedPopupSystem _popups = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
Expand Down Expand Up @@ -97,7 +104,7 @@ private void OnDoAfterFinished(InteractionVerbDoAfterEvent ev)
public bool StartVerb(InteractionVerbPrototype proto, InteractionArgs args, bool force = false)
{
if (!TryComp<OwnInteractionVerbsComponent>(args.User, out var ownInteractions)
|| !force && CheckVerbCooldown(proto, args, out _, ownInteractions))
|| !force && !CheckVerbCooldown(proto, args, out _, ownInteractions))
return false;

// If contest advantage wasn't calculated yet, calculate it now and ensure it's in the allowed range
Expand Down Expand Up @@ -201,43 +208,24 @@ private void AddAll<T>(IEnumerable<InteractionVerbPrototype> verbs, GetVerbsEven
DebugTools.AssertNotEqual(proto.Abstract, true, "Attempted to add a verb with an abstract prototype.");

var name = proto.Name;
if (!proto.AllowSelfInteract && args.User == args.Target
|| args.Verbs.Any(v => v.Text == name)
|| !Transform(args.User).Coordinates.TryDistance(EntityManager, Transform(args.Target).Coordinates, out var distance)
)
if (args.Verbs.Any(v => v.Text == name))
continue;

var isInvalid = proto.RequiresHands && args.Hands is null
|| proto.RequiresCanInteract && !args.CanInteract
|| !proto.Range.IsInRange(distance);

var verbArgs = InteractionArgs.From(args);
// Calculate contest advantage early if required
if (proto.ContestAdvantageRange is not null)
{
CalculateAdvantage(proto, ref verbArgs, out var canPerform);
isInvalid |= !canPerform;
}

var isRequirementMet = proto.Requirement?.IsMet(verbArgs, proto, _verbDependencies) != false;
if (!isRequirementMet && proto.HideByRequirement)
continue;
var isEnabled = PerformChecks(proto, ref verbArgs, out var skipAdding, out var errorLocale);

// TODO: we skip this check since the client is not aware of actions. This should be changed, maybe make actions mixed server/client?
var isAllowed = proto.Action?.IsAllowed(verbArgs, proto, _verbDependencies) == true || _net.IsClient;
if (!isAllowed && proto.HideWhenInvalid)
if (skipAdding)
continue;

var verb = factory.Invoke();
CopyVerbData(proto, verb);
verb.Disabled = isInvalid || !isRequirementMet || !isAllowed;
if (!verb.Disabled)
verb.Act = () => StartVerb(proto, verbArgs);
else
verb.Message = Loc.GetString("interaction-verb-invalid");

// This just marks the verb as disabled without removing the action so the user can still try to use it.
if (CheckVerbCooldown(proto, verbArgs, out var remainingTime, ownInteractions))
verb.Act = () => StartVerb(proto, verbArgs);
verb.Disabled = !isEnabled;

if (!isEnabled)
verb.Message = Loc.GetString(errorLocale!);

if (isEnabled && !CheckVerbCooldown(proto, verbArgs, out var remainingTime, ownInteractions))
{
verb.Disabled = true;
verb.Message = Loc.GetString("interaction-verb-cooldown", ("seconds", remainingTime.TotalSeconds));
Expand All @@ -247,6 +235,64 @@ private void AddAll<T>(IEnumerable<InteractionVerbPrototype> verbs, GetVerbsEven
}
}

/// <summary>
/// Performs all requirement/action checks on the verb. Returns true if the verb can be executed right now.
/// The skipAdding output param indicates whether the caller should skip adding this verb to the verb list, if applicable.
/// </summary>
private bool PerformChecks(InteractionVerbPrototype proto, ref InteractionArgs args, out bool skipAdding, [NotNullWhen(false)] out string? errorLocale)
{
if (!proto.AllowSelfInteract && args.User == args.Target
|| !Transform(args.User).Coordinates.TryDistance(EntityManager, Transform(args.Target).Coordinates, out var distance))
{
skipAdding = true;
errorLocale = "interaction-verb-invalid-target";
return false;
}

if (proto.Requirement?.IsMet(args, proto, _verbDependencies) == false)
{
skipAdding = proto.HideByRequirement;
errorLocale = "interaction-verb-invalid";
return false;
}

// TODO: we skip this check since the client is not aware of actions. This should be changed, maybe make actions mixed server/client?
if (proto.Action?.IsAllowed(args, proto, _verbDependencies) != true && !_net.IsClient)
{
skipAdding = proto.HideWhenInvalid;
errorLocale = "interaction-verb-invalid";
return false;
}

skipAdding = false;
if (proto.RequiresHands && !args.HasHands)
{
errorLocale = "interaction-verb-no-hands";
return false;
}

if (proto.RequiresCanInteract && args is not { CanInteract: true, CanAccess: true } || !proto.Range.IsInRange(distance))
{
errorLocale = "interaction-verb-cannot-reach";
return false;
}

// Calculate contest advantage early if required
if (proto.ContestAdvantageRange is not null)
{
CalculateAdvantage(proto, ref args, out var canPerform);

if (!canPerform)
{
errorLocale = "interaction-verb-too-" + (args.ContestAdvantage > 1f ? "strong" : "weak");
return false;
}
}

errorLocale = null;
return true;
}

/// <summary>
/// Calculates the effective contest advantage for the verb and writes their clamped value to <see cref="InteractionArgs.ContestAdvantage"/>.
/// </summary>
Expand Down Expand Up @@ -282,7 +328,7 @@ private void CopyVerbData(InteractionVerbPrototype proto, Verb verb)
}

/// <summary>
/// Checks if the verb is on cooldown. Returns true if it still is.
/// Checks if the verb is on cooldown. Returns true if the verb can be used right now.
/// </summary>
private bool CheckVerbCooldown(InteractionVerbPrototype proto, InteractionArgs args, out TimeSpan remainingTime, OwnInteractionVerbsComponent? comp = null)
{
Expand All @@ -292,10 +338,10 @@ private bool CheckVerbCooldown(InteractionVerbPrototype proto, InteractionArgs a

var cooldownTarget = proto.GlobalCooldown ? EntityUid.Invalid : args.Target;
if (!comp.Cooldowns.TryGetValue((proto.ID, cooldownTarget), out var cooldown))
return false;
return true;

remainingTime = cooldown - _timing.CurTime;
return remainingTime > TimeSpan.Zero;
return remainingTime <= TimeSpan.Zero;
}

private void StartVerbCooldown(InteractionVerbPrototype proto, InteractionArgs args, TimeSpan cooldown, OwnInteractionVerbsComponent? comp = null)
Expand Down
5 changes: 5 additions & 0 deletions Resources/Locale/en-US/interaction/verbs/core.ftl
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
interaction-verb-invalid = Some requirements for this verb are not met. You cannot use it right now.
interaction-verb-cooldown = This verb is on cooldown. Wait {TOSTRING($seconds, "F1")} seconds.
interaction-verb-too-strong = You are too strong to use this verb.
interaction-verb-too-weak = You are too weak to use this verb.
interaction-verb-invalid-target = You cannot use this verb on that target.
interaction-verb-no-hands = You have no usable hands.
interaction-verb-cannot-reach = You cannot reach there.
2 changes: 2 additions & 0 deletions Resources/Locale/en-US/interaction/verbs/help.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ interaction-HelpUp-name = Help up
interaction-HelpUp-description = Help the person get up.
interaction-HelpUp-delayed-self-popup = You try to help {THE($target)} get up...
interaction-HelpUp-delayed-target-popup = {THE($user)} tries to help you get up...
interaction-HelpUp-delayed-others-popup = {THE($user)} tries to help {THE($target)} get up...
interaction-HelpUp-success-self-popup = You help {THE($target)} get up.
interaction-HelpUp-success-target-popup = {THE($user)} helps you up.
interaction-HelpUp-success-others-popup = {THE($user)} helps {THE($target)} up.
Expand All @@ -12,6 +13,7 @@ interaction-ForceDown-name = Force down
interaction-ForceDown-description = Force the person to lay down on the floor.
interaction-ForceDown-delayed-self-popup = You try to force {THE($target)} down...
interaction-ForceDown-delayed-target-popup = {THE($user)} tries to force you down...
interaction-ForceDown-delayed-others-popup = {THE($user)} tries to force {THE($target)} down...
interaction-ForceDown-success-self-popup = You force {THE($target)} to lay down.
interaction-ForceDown-success-target-popup = {THE($user)} forces you to lay down.
interaction-ForceDown-success-others-popup = {THE($user)} forces {THE($target)} to lay down.
Expand Down
2 changes: 1 addition & 1 deletion Resources/Prototypes/Interactions/base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
requiresCanInteract: true
contactInteraction: true
range:
max: 2
max: 1.2
effectSuccess:
popup: Obvious
sound: {path: /Audio/Effects/thudswoosh.ogg}
Expand Down
1 change: 1 addition & 0 deletions Resources/Prototypes/Interactions/noop_interactions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
id: WaveAt
parent: [BaseHands, BaseGlobal]
priority: 3
requiresCanInteract: false
contactInteraction: false
range: {max: 20}
effectSuccess:
Expand Down