using Content.Server.Damage.Components;
+using Content.Shared.CombatMode.Pacification;
using Content.Shared.Damage;
using Content.Shared.Throwing;
{
base.Initialize();
SubscribeLocalEvent<DamageOnLandComponent, LandEvent>(DamageOnLand);
+ SubscribeLocalEvent<DamageOnLandComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
+ }
+
+ /// <summary>
+ /// Prevent Pacified entities from throwing damaging items.
+ /// </summary>
+ private void OnAttemptPacifiedThrow(Entity<DamageOnLandComponent> 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)
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;
SubscribeLocalEvent<EnsnaringComponent, StepTriggerAttemptEvent>(AttemptStepTrigger);
SubscribeLocalEvent<EnsnaringComponent, StepTriggeredEvent>(OnStepTrigger);
SubscribeLocalEvent<EnsnaringComponent, ThrowDoHitEvent>(OnThrowHit);
+ SubscribeLocalEvent<EnsnaringComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
+ }
+
+ private void OnAttemptPacifiedThrow(Entity<EnsnaringComponent> ent, ref AttemptPacifiedThrowEvent args)
+ {
+ args.Cancel("pacified-cannot-throw-snare");
}
private void OnComponentRemove(EntityUid uid, EnsnaringComponent component, ComponentRemove args)
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;
SubscribeLocalEvent<SpillableComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<SpillableComponent, SolutionOverflowEvent>(OnOverflow);
SubscribeLocalEvent<SpillableComponent, SpillDoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<SpillableComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
}
private void OnExamined(EntityUid uid, SpillableComponent component, ExaminedEvent args)
TrySplashSpillAt(uid, Transform(uid).Coordinates, drainedSolution, out _);
}
+ /// <summary>
+ /// Prevent Pacified entities from throwing items that can spill liquids.
+ /// </summary>
+ private void OnAttemptPacifiedThrow(Entity<SpillableComponent> 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<Verb> args)
{
if (!args.CanAccess || !args.CanInteract)
// 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
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;
+/// <summary>
+/// Raised when a Pacified entity attempts to throw something.
+/// The throw is only permitted if this event is not cancelled.
+/// </summary>
+[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; }
+
+ /// <param name="reasonMessageId">
+ /// 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.
+ /// </param>
+ 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<PacifiedComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<PacifiedComponent, ComponentShutdown>(OnShutdown);
+ SubscribeLocalEvent<PacifiedComponent, BeforeThrowEvent>(OnBeforeThrow);
SubscribeLocalEvent<PacifiedComponent, AttackAttemptEvent>(OnAttackAttempt);
}
_actionsSystem.SetEnabled(combatMode.CombatToggleActionEntity, true);
_alertsSystem.ClearAlert(uid, AlertType.Pacified);
}
+
+ private void OnBeforeThrow(Entity<PacifiedComponent> 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);
+ }
}
namespace Content.Shared.CombatMode.Pacification;
/// <summary>
-/// Status effect that disables combat mode.
+/// Status effect that disables combat mode and restricts aggressive actions.
/// </summary>
[RegisterComponent, NetworkedComponent]
[Access(typeof(PacificationSystem))]
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;
SubscribeLocalEvent<EmbeddableProjectileComponent, ThrowDoHitEvent>(OnEmbedThrowDoHit);
SubscribeLocalEvent<EmbeddableProjectileComponent, ActivateInWorldEvent>(OnEmbedActivate);
SubscribeLocalEvent<EmbeddableProjectileComponent, RemoveEmbeddedProjectileEvent>(OnEmbedRemove);
+ SubscribeLocalEvent<EmbeddableProjectileComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
}
private void OnEmbedActivate(EntityUid uid, EmbeddableProjectileComponent component, ActivateInWorldEvent args)
{
public override DoAfterEvent Clone() => this;
}
+
+ /// <summary>
+ /// Prevent players with the Pacified status effect from throwing embeddable projectiles.
+ /// </summary>
+ private void OnAttemptPacifiedThrow(Entity<EmbeddableProjectileComponent> ent, ref AttemptPacifiedThrowEvent args)
+ {
+ args.Cancel("pacified-cannot-throw-embed");
+ }
}
[Serializable, NetSerializable]
using System.Numerics;
-namespace Content.Shared.Throwing
+namespace Content.Shared.Throwing;
+
+[ByRefEvent]
+public struct BeforeThrowEvent
{
- public sealed class BeforeThrowEvent : HandledEntityEventArgs
+ public BeforeThrowEvent(EntityUid itemUid, Vector2 direction, float throwStrength, EntityUid playerUid)
{
- 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; }
+ 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;
}
--- /dev/null
+
+## 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?!