diff --git a/Content.Shared/Delivery/SharedDeliverySystem.cs b/Content.Shared/Delivery/SharedDeliverySystem.cs index ffbf520610..53c5224940 100644 --- a/Content.Shared/Delivery/SharedDeliverySystem.cs +++ b/Content.Shared/Delivery/SharedDeliverySystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Interaction.Events; using Content.Shared.NameModifier.EntitySystems; using Content.Shared.Objectives.Components; using Content.Shared.Popups; +using Content.Shared.Tools.Components; using Content.Shared.Tag; using Content.Shared.Verbs; using Robust.Shared.Audio.Systems; @@ -41,6 +42,8 @@ public abstract class SharedDeliverySystem : EntitySystem SubscribeLocalEvent(OnDeliveryExamine); SubscribeLocalEvent(OnUseInHand); SubscribeLocalEvent>(OnGetDeliveryVerbs); + SubscribeLocalEvent(OnAttemptSimpleToolUse); + SubscribeLocalEvent(OnSimpleToolUse); SubscribeLocalEvent(OnSpawnerExamine); SubscribeLocalEvent>(OnGetSpawnerVerbs); @@ -100,6 +103,24 @@ public abstract class SharedDeliverySystem : EntitySystem }); } + + private void OnAttemptSimpleToolUse(Entity ent, ref AttemptSimpleToolUseEvent args) + { + if (ent.Comp.IsOpened || !ent.Comp.IsLocked) + args.Cancelled = true; + } + + private void OnSimpleToolUse(Entity ent, ref SimpleToolDoAfterEvent args) + { + if (ent.Comp.IsOpened || args.Cancelled) + return; + + HandlePenalty(ent); + + TryUnlockDelivery(ent, args.User, false, true); + OpenDelivery(ent, args.User, false, true); + } + private void OnGetSpawnerVerbs(Entity ent, ref GetVerbsEvent args) { if (!args.CanAccess || !args.CanInteract || args.Hands == null) @@ -127,33 +148,38 @@ public abstract class SharedDeliverySystem : EntitySystem }); } - private bool TryUnlockDelivery(Entity ent, EntityUid user, bool rewardMoney = true) + private bool TryUnlockDelivery(Entity ent, EntityUid user, bool rewardMoney = true, bool force = false) { // Check fingerprint access if there is a reader on the mail - if (TryComp(ent, out var reader) && !_fingerprintReader.IsAllowed((ent, reader), user)) + if (!force && TryComp(ent, out var reader) && !_fingerprintReader.IsAllowed((ent, reader), user)) return false; var deliveryName = _nameModifier.GetBaseName(ent.Owner); - _audio.PlayPredicted(ent.Comp.UnlockSound, user, user); + if (!force) + _audio.PlayPredicted(ent.Comp.UnlockSound, user, user); ent.Comp.IsLocked = false; UpdateAntiTamperVisuals(ent, ent.Comp.IsLocked); DirtyField(ent, ent.Comp, nameof(DeliveryComponent.IsLocked)); + RemCompDeferred(ent); // we don't want unlocked mail to still be cuttable + var ev = new DeliveryUnlockedEvent(user); RaiseLocalEvent(ent, ref ev); if (rewardMoney) GrantSpesoReward(ent.AsNullable()); - _popup.PopupPredicted(Loc.GetString("delivery-unlocked-self", ("delivery", deliveryName)), - Loc.GetString("delivery-unlocked-others", ("delivery", deliveryName), ("recipient", Identity.Name(user, EntityManager)), ("possadj", user)), user, user); + if (!force) + _popup.PopupPredicted(Loc.GetString("delivery-unlocked-self", ("delivery", deliveryName)), + Loc.GetString("delivery-unlocked-others", ("delivery", deliveryName), ("recipient", Identity.Name(user, EntityManager)), ("possadj", user)), user, user); + return true; } - private void OpenDelivery(Entity ent, EntityUid user, bool attemptPickup = true) + private void OpenDelivery(Entity ent, EntityUid user, bool attemptPickup = true, bool force = false) { var deliveryName = _nameModifier.GetBaseName(ent.Owner); @@ -170,12 +196,13 @@ public abstract class SharedDeliverySystem : EntitySystem _tag.AddTags(ent, TrashTag, RecyclableTag); EnsureComp(ent); - RemComp(ent); // opened mail should not count for the objective + RemCompDeferred(ent); // opened mail should not count for the objective DirtyField(ent.Owner, ent.Comp, nameof(DeliveryComponent.IsOpened)); - _popup.PopupPredicted(Loc.GetString("delivery-opened-self", ("delivery", deliveryName)), - Loc.GetString("delivery-opened-others", ("delivery", deliveryName), ("recipient", Identity.Name(user, EntityManager)), ("possadj", user)), user, user); + if (!force) + _popup.PopupPredicted(Loc.GetString("delivery-opened-self", ("delivery", deliveryName)), + Loc.GetString("delivery-opened-others", ("delivery", deliveryName), ("recipient", Identity.Name(user, EntityManager)), ("possadj", user)), user, user); if (!_container.TryGetContainer(ent, ent.Comp.Container, out var container)) return; @@ -189,7 +216,7 @@ public abstract class SharedDeliverySystem : EntitySystem } else { - _container.EmptyContainer(container, true, Transform(ent.Owner).Coordinates); + _container.EmptyContainer(container, true); } } diff --git a/Content.Shared/FingerprintReader/FingerprintReaderSystem.cs b/Content.Shared/FingerprintReader/FingerprintReaderSystem.cs index e259a17738..aa7d190c34 100644 --- a/Content.Shared/FingerprintReader/FingerprintReaderSystem.cs +++ b/Content.Shared/FingerprintReader/FingerprintReaderSystem.cs @@ -19,7 +19,7 @@ public sealed class FingerprintReaderSystem : EntitySystem /// User trying to gain access. /// True if access was granted, otherwise false. [PublicAPI] - public bool IsAllowed(Entity target, EntityUid user) + public bool IsAllowed(Entity target, EntityUid user, bool showPopup = true) { if (!Resolve(target, ref target.Comp, false)) return true; @@ -30,7 +30,7 @@ public sealed class FingerprintReaderSystem : EntitySystem // Check for gloves first if (!target.Comp.IgnoreGloves && TryGetBlockingGloves(user, out var gloves)) { - if (target.Comp.FailGlovesPopup != null) + if (target.Comp.FailGlovesPopup != null && showPopup) _popup.PopupClient(Loc.GetString(target.Comp.FailGlovesPopup, ("blocker", gloves)), target, user); return false; } @@ -39,7 +39,7 @@ public sealed class FingerprintReaderSystem : EntitySystem if (!TryComp(user, out var fingerprint) || fingerprint.Fingerprint == null || !target.Comp.AllowedFingerprints.Contains(fingerprint.Fingerprint)) { - if (target.Comp.FailPopup != null) + if (target.Comp.FailPopup != null && showPopup) _popup.PopupClient(Loc.GetString(target.Comp.FailPopup), target, user); return false; diff --git a/Content.Shared/Tools/Components/SimpleToolUsageComponent.cs b/Content.Shared/Tools/Components/SimpleToolUsageComponent.cs new file mode 100644 index 0000000000..1f1e9c65f8 --- /dev/null +++ b/Content.Shared/Tools/Components/SimpleToolUsageComponent.cs @@ -0,0 +1,47 @@ +using Content.Shared.DoAfter; +using Content.Shared.Tools.Systems; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Tools.Components; + +/// +/// Component responsible for simple tool interactions. +/// Using a tool with the correct quality on an entity with this component will start a doAfter and raise events. +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(SimpleToolUsageSystem))] +public sealed partial class SimpleToolUsageComponent : Component +{ + /// + /// Tool quality required to use a tool on this. + /// + [DataField] + public ProtoId Quality = "Slicing"; + + /// + /// The duration using a tool on this entity will take in seconds. + /// + [DataField] + public float DoAfter = 5; + + /// + /// What verb should display to allow you to use a tool on this entity. + /// If null, no verb will be shown. + /// + [DataField] + public LocId? UsageVerb; + + /// + /// The message to show when the verb is disabled. + /// + [DataField] + public LocId BlockedMessage = "simple-tool-usage-blocked-message"; +} + +[ByRefEvent] +public record struct AttemptSimpleToolUseEvent(EntityUid User, bool Cancelled = false); + +[Serializable, NetSerializable] +public sealed partial class SimpleToolDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/Tools/Systems/SimpleToolUsageSystem.cs b/Content.Shared/Tools/Systems/SimpleToolUsageSystem.cs new file mode 100644 index 0000000000..0f7da9af8a --- /dev/null +++ b/Content.Shared/Tools/Systems/SimpleToolUsageSystem.cs @@ -0,0 +1,79 @@ +using Content.Shared.DoAfter; +using Content.Shared.Interaction; +using Content.Shared.Tools.Components; +using Content.Shared.Verbs; +using Robust.Shared.Utility; + +namespace Content.Shared.Tools.Systems; + +public sealed partial class SimpleToolUsageSystem : EntitySystem +{ + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedToolSystem _tools = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAfterInteract); + SubscribeLocalEvent>(OnGetInteractionVerbs); + } + + private void OnAfterInteract(Entity ent, ref AfterInteractUsingEvent args) + { + if (!args.CanReach || args.Handled) + return; + + if (!_tools.HasQuality(args.Used, ent.Comp.Quality)) + return; + + AttemptToolUsage(ent, args.User, args.Used); + } + + public void OnGetInteractionVerbs(Entity ent, ref GetVerbsEvent args) + { + if (ent.Comp.UsageVerb == null) + return; + + if (!args.CanAccess || !args.CanInteract) + return; + + var disabled = args.Using == null || !_tools.HasQuality(args.Using.Value, ent.Comp.Quality); + + var used = args.Using; + var user = args.User; + + InteractionVerb verb = new() + { + Act = () => + { + if (used != null) + AttemptToolUsage(ent, user, used.Value); + }, + Disabled = disabled, + Message = disabled ? Loc.GetString(ent.Comp.BlockedMessage, ("quality", ent.Comp.Quality)) : null, + Text = Loc.GetString(ent.Comp.UsageVerb), + }; + + args.Verbs.Add(verb); + } + + private void AttemptToolUsage(Entity ent, EntityUid user, EntityUid tool) + { + var attemptEv = new AttemptSimpleToolUseEvent(user); + RaiseLocalEvent(ent, ref attemptEv); + + if (attemptEv.Cancelled) + return; + + var doAfterArgs = new DoAfterArgs(EntityManager, user, ent.Comp.DoAfter, new SimpleToolDoAfterEvent(), ent, tool) + { + BreakOnDamage = true, + BreakOnDropItem = true, + BreakOnMove = true, + BreakOnHandChange = true, + }; + + _doAfterSystem.TryStartDoAfter(doAfterArgs); + } +} diff --git a/Resources/Locale/en-US/delivery/delivery-component.ftl b/Resources/Locale/en-US/delivery/delivery-component.ftl index fbe4e74937..a6bbc79343 100644 --- a/Resources/Locale/en-US/delivery/delivery-component.ftl +++ b/Resources/Locale/en-US/delivery/delivery-component.ftl @@ -10,6 +10,7 @@ delivery-opened-others = {CAPITALIZE($recipient)} opened the {$delivery}. delivery-unlock-verb = Unlock delivery-open-verb = Open +delivery-slice-verb = Slice open delivery-teleporter-amount-examine = { $amount -> diff --git a/Resources/Locale/en-US/tools/simple-tool-usage.ftl b/Resources/Locale/en-US/tools/simple-tool-usage.ftl new file mode 100644 index 0000000000..fd6ae49d22 --- /dev/null +++ b/Resources/Locale/en-US/tools/simple-tool-usage.ftl @@ -0,0 +1 @@ +simple-tool-usage-blocked-message = You need a tool that can perform {$quality}! diff --git a/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml b/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml index b396b20a46..19789cd7eb 100644 --- a/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml +++ b/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml @@ -41,6 +41,9 @@ delivery: !type:Container - type: StealTarget stealGroup: Mail + - type: SimpleToolUsage + doAfter: 4 + usageVerb: delivery-slice-verb - type: entity parent: BaseDelivery @@ -75,9 +78,11 @@ size: Huge - type: Delivery baseSpesoReward: 1000 - baseSpesoPenalty: 500 + baseSpesoPenalty: 250 # So low due to dept economy splitting all the earnings, but not splitting the penalty. - type: Speech speechVerb: Robotic + - type: SimpleToolUsage + doAfter: 6 - type: EntityTableContainerFill containers: delivery: !type:NestedSelector @@ -115,7 +120,7 @@ storedRotation: 90 - type: Delivery baseSpesoReward: 500 - baseSpesoPenalty: 250 + baseSpesoPenalty: 125 # So low due to dept economy splitting all the earnings, but not splitting the penalty. - type: Speech speechVerb: Robotic - type: EntityTableContainerFill