using Content.Shared.CCVar; using Content.Shared.Chemistry.Events; using Content.Shared.Climbing.Events; using Content.Shared.Climbing.Systems; using Content.Shared.Clumsy.Components; using Content.Shared.Damage.Systems; using Content.Shared.Hands; using Content.Shared.IdentityManagement; using Content.Shared.Medical; using Content.Shared.Popups; using Content.Shared.Random.Helpers; using Content.Shared.StatusEffectNew; using Content.Shared.Stunnable; using Content.Shared.Throwing; using Content.Shared.Weapons.Ranged.Events; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.Network; using Robust.Shared.Timing; namespace Content.Shared.Clumsy; /// /// Handles status effects which cause the afflicted to randomly fail certain events. /// public sealed partial class ClumsyStatusEffectSystem : EntitySystem { [Dependency] private SharedStunSystem _stun = default!; [Dependency] private SharedAudioSystem _audio = default!; [Dependency] private SharedPopupSystem _popup = default!; [Dependency] private ClimbSystem _climb = default!; [Dependency] private DamageableSystem _damageable = default!; [Dependency] private IConfigurationManager _cfg = default!; [Dependency] private IGameTiming _timing = default!; [Dependency] private INetManager _net = default!; #region Subscriptions /// Clumsy people are bad at baseball! [SubscribeLocalEvent] private void OnCatchAttemptEvent(Entity status, ref StatusEffectRelayedEvent args) { if (args.Args.Cancelled || !SharedRandomExtensions.PredictedProb(_timing, status.Comp.ClumsyChance, GetNetEntity(status), GetNetEntity(args.AppliedTo))) return; // Song and dance :o| var ev = args.Args; ev.Cancelled = true; args.Args = ev; if (status.Comp.FailDamage != null) _damageable.ChangeDamage(args.AppliedTo, status.Comp.FailDamage, origin: args.Args.Item); var identity = Identity.Entity(args.AppliedTo, EntityManager); var selfMessage = status.Comp.SelfFailedMessage == null ? null : Loc.GetString(status.Comp.SelfFailedMessage, ("item", args.Args.Item)); var othersMessage = status.Comp.OtherFailedMessage == null ? null : Loc.GetString(status.Comp.OtherFailedMessage, ("item", args.Args.Item), ("catcher", identity)); _popup.PopupEntity(selfMessage, othersMessage, args.AppliedTo, args.AppliedTo); // _audio.PlayPredicted doesn't play nice with collision events so we need PlayPvs // exit early for clients so the sound doesn't play twice if (_net.IsClient) return; _audio.PlayPvs(status.Comp.ClumsySound, args.AppliedTo); } /// Clumsy people shock themselves with defibrillators! [SubscribeLocalEvent] private void OnBeforeDefibrillatorZapsEvent(Entity status, ref StatusEffectRelayedEvent args) { if (!SharedRandomExtensions.PredictedProb(_timing, status.Comp.ClumsyChance, GetNetEntity(status), GetNetEntity(args.AppliedTo))) return; var ev = args.Args; ev.DefibTarget = ev.EntityUsingDefib; if (status.Comp.FailedMessage != null) _popup.PopupEntity(Loc.GetString(status.Comp.FailedMessage), args.AppliedTo, args.AppliedTo); _audio.PlayPredicted(status.Comp.ClumsySound, args.AppliedTo, args.AppliedTo); } /// Especially clumsy people may sometimes fail to pick things up, and fail to hold on to things they are given. [SubscribeLocalEvent] private void OnBeforeEquippingHandEvent(Entity status, ref StatusEffectRelayedEvent args) { if (args.Args.Cancelled || !SharedRandomExtensions.PredictedProb(_timing, status.Comp.ClumsyChance, GetNetEntity(status), GetNetEntity(args.AppliedTo))) return; var ev = args.Args; ev.Cancelled = true; args.Args = ev; var selfMessage = status.Comp.SelfFailedMessage == null ? null : Loc.GetString(status.Comp.SelfFailedMessage, ("item", args.Args.Item)); var othersMessage = status.Comp.OtherFailedMessage == null ? null : Loc.GetString(status.Comp.OtherFailedMessage, ("item", args.Args.Item)); _popup.PopupEntity(selfMessage, othersMessage, args.AppliedTo, args.AppliedTo); } /// Clumsy people can't be trusted with guns! [SubscribeLocalEvent] private void OnBeforeGunShotEvent(Entity status, ref StatusEffectRelayedEvent args) { if (args.Args.Cancelled || args.Args.Gun.Comp.ClumsyProof || !SharedRandomExtensions.PredictedProb(_timing, status.Comp.ClumsyChance, GetNetEntity(status), GetNetEntity(args.AppliedTo))) return; args.Args.Cancel(); if (status.Comp.FailDamage != null) _damageable.ChangeDamage(args.AppliedTo, status.Comp.FailDamage, origin: args.Args.Gun); _stun.TryUpdateParalyzeDuration(args.AppliedTo, status.Comp.StunDuration); if (status.Comp.FailedMessage != null) _popup.PopupEntity(Loc.GetString(status.Comp.FailedMessage, ("gun", args.Args.Gun)), args.AppliedTo, args.AppliedTo); // SelfBeforeGunShotEvent is raised on server so _audio.PlayPredicted fails to play locally if (_net.IsClient) return; // Apply salt to the wound ("Honk!") (No idea what this comment means) :o) _audio.PlayPvs(status.Comp.GunShootFailSound, args.Args.Gun); _audio.PlayPvs(status.Comp.ClumsySound, args.AppliedTo); } /// Clumsy people sometimes inject themselves! [SubscribeLocalEvent] private void OnBeforeInjectEvent(Entity status, ref StatusEffectRelayedEvent args) { if (!SharedRandomExtensions.PredictedProb(_timing, status.Comp.ClumsyChance, GetNetEntity(status), GetNetEntity(args.AppliedTo))) return; var ev = args.Args; ev.TargetGettingInjected = ev.EntityUsingInjector; if (status.Comp.FailedMessage != null) ev.OverrideMessage = Loc.GetString(status.Comp.FailedMessage); _audio.PlayPredicted(status.Comp.ClumsySound, args.AppliedTo, args.AppliedTo); } /// Clumsy people have a blood feud with tables! [SubscribeLocalEvent] private void OnBeforeClimbEvent(Entity status, ref StatusEffectRelayedEvent args) { if (args.Args.Cancelled || !_cfg.GetCVar(CCVars.GameTableBonk) || !SharedRandomExtensions.PredictedProb(_timing, status.Comp.ClumsyChance, GetNetEntity(status), GetNetEntity(args.AppliedTo))) return; args.Args.Cancel(); _climb.Bonk(args.Args.BeingClimbedOn.Owner, args.Args.GettingPutOnTable); var putOnTable = Identity.Entity(args.Args.GettingPutOnTable, EntityManager); var puttingOnTable = Identity.Entity(args.Args.PuttingOnTable, EntityManager); if (args.Args.PuttingOnTable == args.Args.GettingPutOnTable) { // You are slamming yourself onto the table. var selfMessage = status.Comp.SelfFailedMessage == null ? null : Loc.GetString(status.Comp.SelfFailedMessage, ("bonkable", args.Args.BeingClimbedOn)); var othersMessage = status.Comp.OtherFailedMessage == null ? null : Loc.GetString(status.Comp.OtherFailedMessage, ("victim", putOnTable), ("bonkable", args.Args.BeingClimbedOn)); _popup.PopupEntity(selfMessage, othersMessage, args.AppliedTo, args.AppliedTo); } else { // Someone else slammed you onto the table. // This is only run in server so you need to use popup entity. var message = status.Comp.ForcedMessage == null ? null : Loc.GetString(status.Comp.ForcedMessage, ("bonker", puttingOnTable), ("victim", putOnTable), ("bonkable", args.Args.BeingClimbedOn)); _popup.PopupEntity(message, args.AppliedTo); } _audio.PlayPredicted(status.Comp.ClumsySound, args.Args.GettingPutOnTable, args.Args.GettingPutOnTable); } #endregion }