diff --git a/Content.Server/Damage/Systems/DamageOnLandSystem.cs b/Content.Server/Damage/Systems/DamageOnLandSystem.cs index f44bfeacce..8f01e791ea 100644 --- a/Content.Server/Damage/Systems/DamageOnLandSystem.cs +++ b/Content.Server/Damage/Systems/DamageOnLandSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Damage.Components; +using Content.Shared.CombatMode.Pacification; using Content.Shared.Damage; using Content.Shared.Throwing; @@ -12,6 +13,19 @@ namespace Content.Server.Damage.Systems { base.Initialize(); SubscribeLocalEvent(DamageOnLand); + SubscribeLocalEvent(OnAttemptPacifiedThrow); + } + + /// + /// Prevent Pacified entities from throwing damaging items. + /// + private void OnAttemptPacifiedThrow(Entity ent, ref AttemptPacifiedThrowEvent args) + { + // Allow healing projectiles, forbid any that do damage: + if (ent.Comp.Damage.Any()) + { + args.Cancel("pacified-cannot-throw"); + } } private void DamageOnLand(EntityUid uid, DamageOnLandComponent component, ref LandEvent args) diff --git a/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs b/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs index b04c1296fc..f355187a0b 100644 --- a/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs +++ b/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Server.Body.Systems; using Content.Shared.Alert; using Content.Shared.Body.Part; +using Content.Shared.CombatMode.Pacification; using Content.Shared.Damage.Components; using Content.Shared.Damage.Systems; using Content.Shared.DoAfter; @@ -26,6 +27,12 @@ public sealed partial class EnsnareableSystem SubscribeLocalEvent(AttemptStepTrigger); SubscribeLocalEvent(OnStepTrigger); SubscribeLocalEvent(OnThrowHit); + SubscribeLocalEvent(OnAttemptPacifiedThrow); + } + + private void OnAttemptPacifiedThrow(Entity ent, ref AttemptPacifiedThrowEvent args) + { + args.Cancel("pacified-cannot-throw-snare"); } private void OnComponentRemove(EntityUid uid, EnsnaringComponent component, ComponentRemove args) diff --git a/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs b/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs index a6d6a5b204..78bdfc6d06 100644 --- a/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs +++ b/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs @@ -6,6 +6,7 @@ using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using Content.Shared.Clothing.Components; +using Content.Shared.CombatMode.Pacification; using Content.Shared.Database; using Content.Shared.DoAfter; using Content.Shared.Examine; @@ -36,6 +37,7 @@ public sealed partial class PuddleSystem SubscribeLocalEvent(OnGotEquipped); SubscribeLocalEvent(OnOverflow); SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnAttemptPacifiedThrow); } private void OnExamined(EntityUid uid, SpillableComponent component, ExaminedEvent args) @@ -152,6 +154,22 @@ public sealed partial class PuddleSystem TrySplashSpillAt(uid, Transform(uid).Coordinates, drainedSolution, out _); } + /// + /// Prevent Pacified entities from throwing items that can spill liquids. + /// + private void OnAttemptPacifiedThrow(Entity ent, ref AttemptPacifiedThrowEvent args) + { + // Don’t care about closed containers. + if (_openable.IsClosed(ent)) + return; + + // Don’t care about empty containers. + if (!_solutionContainerSystem.TryGetSolution(ent, ent.Comp.SolutionName, out var solution)) + return; + + args.Cancel("pacified-cannot-throw-spill"); + } + private void AddSpillVerb(EntityUid uid, SpillableComponent component, GetVerbsEvent args) { if (!args.CanAccess || !args.CanInteract) diff --git a/Content.Server/Hands/Systems/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs index 5ceb4a8d60..5c750e7544 100644 --- a/Content.Server/Hands/Systems/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -204,9 +204,9 @@ namespace Content.Server.Hands.Systems // Let other systems change the thrown entity (useful for virtual items) // or the throw strength. var ev = new BeforeThrowEvent(throwEnt, direction, throwStrength, player); - RaiseLocalEvent(player, ev, false); + RaiseLocalEvent(player, ref ev); - if (ev.Handled) + if (ev.Cancelled) return true; // This can grief the above event so we raise it afterwards diff --git a/Content.Shared/CombatMode/Pacification/PacificationSystem.cs b/Content.Shared/CombatMode/Pacification/PacificationSystem.cs index c52605e09f..a1332fec76 100644 --- a/Content.Shared/CombatMode/Pacification/PacificationSystem.cs +++ b/Content.Shared/CombatMode/Pacification/PacificationSystem.cs @@ -1,20 +1,57 @@ using Content.Shared.Actions; using Content.Shared.Alert; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction.Events; +using Content.Shared.Popups; +using Content.Shared.Throwing; namespace Content.Shared.CombatMode.Pacification; +/// +/// Raised when a Pacified entity attempts to throw something. +/// The throw is only permitted if this event is not cancelled. +/// +[ByRefEvent] +public struct AttemptPacifiedThrowEvent +{ + public EntityUid ItemUid; + public EntityUid PlayerUid; + + public AttemptPacifiedThrowEvent(EntityUid itemUid, EntityUid playerUid) + { + ItemUid = itemUid; + PlayerUid = playerUid; + } + + public bool Cancelled { get; private set; } = false; + public string? CancelReasonMessageId { get; private set; } + + /// + /// Localization string ID for the reason this event has been cancelled. + /// If null, a generic message will be shown to the player. + /// Note that any supplied localization string MUST accept a '$projectile' + /// parameter specifying the name of the thrown entity. + /// + public void Cancel(string? reasonMessageId = null) + { + Cancelled = true; + CancelReasonMessageId = reasonMessageId; + } +} + public sealed class PacificationSystem : EntitySystem { [Dependency] private readonly AlertsSystem _alertsSystem = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; [Dependency] private readonly SharedCombatModeSystem _combatSystem = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnBeforeThrow); SubscribeLocalEvent(OnAttackAttempt); } @@ -47,4 +84,23 @@ public sealed class PacificationSystem : EntitySystem _actionsSystem.SetEnabled(combatMode.CombatToggleActionEntity, true); _alertsSystem.ClearAlert(uid, AlertType.Pacified); } + + private void OnBeforeThrow(Entity ent, ref BeforeThrowEvent args) + { + var thrownItem = args.ItemUid; + var itemName = Identity.Entity(thrownItem, EntityManager); + + // Raise an AttemptPacifiedThrow event and rely on other systems to check + // whether the candidate item is OK to throw: + var ev = new AttemptPacifiedThrowEvent(thrownItem, ent); + RaiseLocalEvent(thrownItem, ref ev); + if (!ev.Cancelled) + return; + + args.Cancelled = true; + + // Tell the player why they can’t throw stuff: + var cannotThrowMessage = ev.CancelReasonMessageId ?? "pacified-cannot-throw"; + _popup.PopupEntity(Loc.GetString(cannotThrowMessage, ("projectile", itemName)), ent, ent); + } } diff --git a/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs b/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs index 40ddc70002..4b6dff76a2 100644 --- a/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs +++ b/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs @@ -3,7 +3,7 @@ using Robust.Shared.GameStates; namespace Content.Shared.CombatMode.Pacification; /// -/// Status effect that disables combat mode. +/// Status effect that disables combat mode and restricts aggressive actions. /// [RegisterComponent, NetworkedComponent] [Access(typeof(PacificationSystem))] diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs index 3b9eded288..e003764f92 100644 --- a/Content.Shared/Projectiles/SharedProjectileSystem.cs +++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs @@ -1,11 +1,10 @@ using System.Numerics; +using Content.Shared.CombatMode.Pacification; using Content.Shared.Damage; using Content.Shared.DoAfter; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Throwing; -using Content.Shared.Weapons.Ranged.Components; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; using Robust.Shared.Network; @@ -37,6 +36,7 @@ public abstract partial class SharedProjectileSystem : EntitySystem SubscribeLocalEvent(OnEmbedThrowDoHit); SubscribeLocalEvent(OnEmbedActivate); SubscribeLocalEvent(OnEmbedRemove); + SubscribeLocalEvent(OnAttemptPacifiedThrow); } private void OnEmbedActivate(EntityUid uid, EmbeddableProjectileComponent component, ActivateInWorldEvent args) @@ -152,6 +152,14 @@ public abstract partial class SharedProjectileSystem : EntitySystem { public override DoAfterEvent Clone() => this; } + + /// + /// Prevent players with the Pacified status effect from throwing embeddable projectiles. + /// + private void OnAttemptPacifiedThrow(Entity ent, ref AttemptPacifiedThrowEvent args) + { + args.Cancel("pacified-cannot-throw-embed"); + } } [Serializable, NetSerializable] diff --git a/Content.Shared/Throwing/BeforeThrowEvent.cs b/Content.Shared/Throwing/BeforeThrowEvent.cs index 546bf26d8e..36e7dd758b 100644 --- a/Content.Shared/Throwing/BeforeThrowEvent.cs +++ b/Content.Shared/Throwing/BeforeThrowEvent.cs @@ -1,20 +1,22 @@ using System.Numerics; -namespace Content.Shared.Throwing -{ - public sealed class BeforeThrowEvent : HandledEntityEventArgs - { - public BeforeThrowEvent(EntityUid itemUid, Vector2 direction, float throwStrength, EntityUid playerUid) - { - ItemUid = itemUid; - Direction = direction; - ThrowStrength = throwStrength; - PlayerUid = playerUid; - } +namespace Content.Shared.Throwing; - public EntityUid ItemUid { get; set; } - public Vector2 Direction { get; } - public float ThrowStrength { get; set;} - public EntityUid PlayerUid { get; } +[ByRefEvent] +public struct BeforeThrowEvent +{ + public BeforeThrowEvent(EntityUid itemUid, Vector2 direction, float throwStrength, EntityUid playerUid) + { + ItemUid = itemUid; + Direction = direction; + ThrowStrength = throwStrength; + PlayerUid = playerUid; } + + public EntityUid ItemUid { get; set; } + public Vector2 Direction { get; } + public float ThrowStrength { get; set;} + public EntityUid PlayerUid { get; } + + public bool Cancelled = false; } diff --git a/Resources/Locale/en-US/pacified/pacified.ftl b/Resources/Locale/en-US/pacified/pacified.ftl new file mode 100644 index 0000000000..4d45f13bd3 --- /dev/null +++ b/Resources/Locale/en-US/pacified/pacified.ftl @@ -0,0 +1,11 @@ + +## Messages shown to Pacified players when they try to do violence: + +# With projectiles: +pacified-cannot-throw = You can't bring yourself to throw { THE($projectile) }, that could hurt someone! +# With embedding projectiles: +pacified-cannot-throw-embed = No way you can throw { THE($projectile) }, that could get lodged inside someone! +# With liquid-spilling projectiles: +pacified-cannot-throw-spill = You can't possibly throw { THE($projectile) }, that could spill nasty stuff on someone! +# With bolas and snares: +pacified-cannot-throw-snare = You can't throw { THE($projectile) }, what if someone trips?!