diff --git a/Content.Shared/InteractionVerbs/InteractionArgs.cs b/Content.Shared/InteractionVerbs/InteractionArgs.cs index a5b344be3c..71e33dde63 100644 --- a/Content.Shared/InteractionVerbs/InteractionArgs.cs +++ b/Content.Shared/InteractionVerbs/InteractionArgs.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Content.Shared.Hands.Components; using Content.Shared.Verbs; using Robust.Shared.Serialization; @@ -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; /// /// A float value between 0 and positive infinity that indicates how much stronger the user @@ -27,19 +28,20 @@ public sealed partial class InteractionArgs public Dictionary Blackboard => _blackboardField ??= new(3); private Dictionary? _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(GetVerbsEvent ev) where T : Verb => new(ev.User, ev.Target, ev.Using, ev.CanAccess, ev.CanInteract, null); + public static InteractionArgs From(GetVerbsEvent ev) where T : Verb => new(ev.User, ev.Target, ev.Using, ev.CanAccess, ev.CanInteract, ev.Hands is not null, null); /// /// Tries to get a value from the blackboard as an instance of a specific type. diff --git a/Content.Shared/InteractionVerbs/SharedInteractionVerbsSystem.cs b/Content.Shared/InteractionVerbs/SharedInteractionVerbsSystem.cs index f32a74b18b..ed74af8bc6 100644 --- a/Content.Shared/InteractionVerbs/SharedInteractionVerbsSystem.cs +++ b/Content.Shared/InteractionVerbs/SharedInteractionVerbsSystem.cs @@ -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; @@ -22,9 +26,12 @@ public abstract class SharedInteractionVerbsSystem : EntitySystem private readonly InteractionAction.VerbDependencies _verbDependencies = new(); private List _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!; @@ -97,7 +104,7 @@ private void OnDoAfterFinished(InteractionVerbDoAfterEvent ev) public bool StartVerb(InteractionVerbPrototype proto, InteractionArgs args, bool force = false) { if (!TryComp(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 @@ -201,43 +208,24 @@ private void AddAll(IEnumerable 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)); @@ -247,6 +235,64 @@ private void AddAll(IEnumerable verbs, GetVerbsEven } } + /// + /// 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. + /// + 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; + } + /// /// Calculates the effective contest advantage for the verb and writes their clamped value to . /// @@ -282,7 +328,7 @@ private void CopyVerbData(InteractionVerbPrototype proto, Verb verb) } /// - /// 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. /// private bool CheckVerbCooldown(InteractionVerbPrototype proto, InteractionArgs args, out TimeSpan remainingTime, OwnInteractionVerbsComponent? comp = null) { @@ -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) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index dc07447c65..2865f548ee 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -5862,3 +5862,30 @@ Entries: id: 6314 time: '2024-09-03T02:17:55.0000000+00:00' url: https://github.com/Simple-Station/Einstein-Engines/pull/808 +- author: VMSolidus + changes: + - type: Add + message: >- + Several new tips have been added to the game, many of which reference + new content available on Einstein-Engines. + id: 6315 + time: '2024-09-05T00:00:21.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/844 +- author: Mnemotechnician + changes: + - type: Fix + message: Fixed a couple issues with the new interaction verb system. + id: 6316 + time: '2024-09-05T00:18:41.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/854 +- author: VMSolidus + changes: + - type: Add + message: >- + Due to NUMEROUS complaints, NanoTrasen has swapped the sticker labels on + all colored jumpskirts to correctly state that they are infact, + "Skirts", so now they can legally be worn by Harpies, Lamia, and + Arachne. + id: 6317 + time: '2024-09-05T00:19:49.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/848 diff --git a/Resources/Locale/en-US/interaction/verbs/core.ftl b/Resources/Locale/en-US/interaction/verbs/core.ftl index 42912b8bb4..5cdb634223 100644 --- a/Resources/Locale/en-US/interaction/verbs/core.ftl +++ b/Resources/Locale/en-US/interaction/verbs/core.ftl @@ -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. diff --git a/Resources/Locale/en-US/interaction/verbs/help.ftl b/Resources/Locale/en-US/interaction/verbs/help.ftl index 3b21d7573c..a615dcd9a5 100644 --- a/Resources/Locale/en-US/interaction/verbs/help.ftl +++ b/Resources/Locale/en-US/interaction/verbs/help.ftl @@ -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. @@ -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. diff --git a/Resources/Prototypes/Datasets/tips.yml b/Resources/Prototypes/Datasets/tips.yml index f80093a0ae..6be2b9d03f 100644 --- a/Resources/Prototypes/Datasets/tips.yml +++ b/Resources/Prototypes/Datasets/tips.yml @@ -113,7 +113,6 @@ - "Вы можете убрать предмет с дороги, перетащив его, а затем, удерживая CTRL + правый клик, переместите мышь в нужное вам направление." - "При общении со службой безопасности часто можно полностью отменить приговор благодаря сотрудничеству и обману." - "Огонь может распространяться на других игроков через прикосновение! Будьте осторожны рядом с пылающими телами или большими толпами, в которых есть горящие люди." - - "Пробоины в корпусе занимают несколько секунд, чтобы полностью заполнить пространство. Вы можете использовать это время, чтобы залатать пробоину, если вы достаточно уверены в себе, или просто убежать." - "Урон от ожогов, например от сварочного инструмента или лампочки, можно использовать для прижигания ран и остановки кровотечения." - "Кровотечение - это не шутка! Если в вас стреляли или вы получили другое серьезное ранение, позаботьтесь о том, чтобы быстро вылечить его." - "В экстренной ситуации вы можете разрезать комбинезон острым предметом, чтобы получить ткань, из которой можно сделать марлю." diff --git a/Resources/Prototypes/Entities/Clothing/Uniforms/color_jumpskirts.yml b/Resources/Prototypes/Entities/Clothing/Uniforms/color_jumpskirts.yml index 1f77059841..a2bf6b687a 100644 --- a/Resources/Prototypes/Entities/Clothing/Uniforms/color_jumpskirts.yml +++ b/Resources/Prototypes/Entities/Clothing/Uniforms/color_jumpskirts.yml @@ -1,6 +1,6 @@ # White Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorWhite name: white jumpskirt description: A generic white jumpskirt with no rank markings. @@ -27,7 +27,7 @@ # Grey Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorGrey name: grey jumpskirt description: A tasteful grey jumpskirt that reminds you of the good old days. @@ -58,7 +58,7 @@ # Black Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorBlack name: black jumpskirt description: A generic black jumpskirt with no rank markings. @@ -89,7 +89,7 @@ # Blue Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorBlue name: blue jumpskirt description: A generic blue jumpskirt with no rank markings. @@ -120,7 +120,7 @@ # Dark Blue Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorDarkBlue name: dark blue jumpskirt description: A generic dark blue jumpskirt with no rank markings. @@ -151,7 +151,7 @@ # Teal Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorTeal name: teal jumpskirt description: A generic teal jumpskirt with no rank markings. @@ -182,7 +182,7 @@ # Green Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorGreen name: green jumpskirt description: A generic green jumpskirt with no rank markings. @@ -213,7 +213,7 @@ # Dark Green Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorDarkGreen name: dark green jumpskirt description: A generic dark green jumpskirt with no rank markings. @@ -244,7 +244,7 @@ # Orange Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorOrange name: orange jumpskirt description: Don't wear this near paranoid security officers. @@ -275,7 +275,7 @@ # Pink Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorPink name: pink jumpskirt description: Just looking at this makes you feel fabulous. @@ -306,7 +306,7 @@ # Red Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorRed name: red jumpskirt description: A generic red jumpskirt with no rank markings. @@ -337,7 +337,7 @@ # Yellow Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorYellow name: yellow jumpskirt description: A generic yellow jumpskirt with no rank markings. @@ -368,7 +368,7 @@ # Purple Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorPurple name: purple jumpskirt description: A generic light purple jumpskirt with no rank markings. @@ -399,7 +399,7 @@ # Light Brown Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorLightBrown name: light brown jumpskirt description: A generic light brown jumpskirt with no rank markings. @@ -430,7 +430,7 @@ # Brown Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorBrown name: brown jumpskirt description: A generic brown jumpskirt with no rank markings. @@ -461,7 +461,7 @@ # Maroon Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorMaroon name: maroon jumpskirt description: A generic maroon jumpskirt with no rank markings. diff --git a/Resources/Prototypes/Interactions/base.yml b/Resources/Prototypes/Interactions/base.yml index 6d764c9a5e..82d8574c1e 100644 --- a/Resources/Prototypes/Interactions/base.yml +++ b/Resources/Prototypes/Interactions/base.yml @@ -20,7 +20,7 @@ requiresCanInteract: true contactInteraction: true range: - max: 2 + max: 1.2 effectSuccess: popup: Obvious sound: {path: /Audio/Effects/thudswoosh.ogg} diff --git a/Resources/Prototypes/Interactions/noop_interactions.yml b/Resources/Prototypes/Interactions/noop_interactions.yml index af79c60877..573f1f7791 100644 --- a/Resources/Prototypes/Interactions/noop_interactions.yml +++ b/Resources/Prototypes/Interactions/noop_interactions.yml @@ -19,6 +19,7 @@ id: WaveAt parent: [BaseHands, BaseGlobal] priority: 3 + requiresCanInteract: false contactInteraction: false range: {max: 20} effectSuccess: