using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Inventory; using Content.Shared.StatusEffect; using Content.Shared.StepTrigger.Systems; using Content.Shared.Stunnable; using JetBrains.Annotations; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; namespace Content.Shared.Slippery; [UsedImplicitly] public sealed class SlipperySystem : EntitySystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedStunSystem _stun = default!; [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(HandleAttemptCollide); SubscribeLocalEvent(HandleStepTrigger); SubscribeLocalEvent(OnNoSlipAttempt); // as long as slip-resistant mice are never added, this should be fine (otherwise a mouse-hat will transfer it's power to the wearer). SubscribeLocalEvent>((e, c, ev) => OnNoSlipAttempt(e, c, ev.Args)); SubscribeLocalEvent(OnSlipperyGetState); SubscribeLocalEvent(OnSlipperyHandleState); } private void OnSlipperyHandleState(EntityUid uid, SlipperyComponent component, ref ComponentHandleState args) { if (args.Current is not SlipperyComponentState state) return; component.ParalyzeTime = state.ParalyzeTime; component.LaunchForwardsMultiplier = state.LaunchForwardsMultiplier; component.SlipSound = new SoundPathSpecifier(state.SlipSound); } private void OnSlipperyGetState(EntityUid uid, SlipperyComponent component, ref ComponentGetState args) { args.State = new SlipperyComponentState(component.ParalyzeTime, component.LaunchForwardsMultiplier, _audio.GetSound(component.SlipSound)); } private void HandleStepTrigger(EntityUid uid, SlipperyComponent component, ref StepTriggeredEvent args) { TrySlip(uid, component, args.Tripper); } private void HandleAttemptCollide( EntityUid uid, SlipperyComponent component, ref StepTriggerAttemptEvent args) { args.Continue |= CanSlip(uid, args.Tripper); } private static void OnNoSlipAttempt(EntityUid uid, NoSlipComponent component, SlipAttemptEvent args) { args.Cancel(); } private bool CanSlip(EntityUid uid, EntityUid toSlip) { return !_container.IsEntityInContainer(uid) && _statusEffects.CanApplyEffect(toSlip, "Stun"); //Should be KnockedDown instead? } private void TrySlip(EntityUid uid, SlipperyComponent component, EntityUid other) { if (HasComp(other)) return; var attemptEv = new SlipAttemptEvent(); RaiseLocalEvent(other, attemptEv); if (attemptEv.Cancelled) return; var ev = new SlipEvent(other); RaiseLocalEvent(uid, ref ev); if (TryComp(other, out PhysicsComponent? physics)) _physics.SetLinearVelocity(other, physics.LinearVelocity * component.LaunchForwardsMultiplier, body: physics); var playSound = !_statusEffects.HasStatusEffect(other, "KnockedDown"); _stun.TryParalyze(other, TimeSpan.FromSeconds(component.ParalyzeTime), true); // Preventing from playing the slip sound when you are already knocked down. if (playSound) { _audio.PlayPredicted(component.SlipSound, other, other); } _adminLogger.Add(LogType.Slip, LogImpact.Low, $"{ToPrettyString(other):mob} slipped on collision with {ToPrettyString(uid):entity}"); } public void CopyConstruct(EntityUid destUid, SlipperyComponent srcSlip) { var destEvaporation = EntityManager.EnsureComponent(destUid); destEvaporation.SlipSound = srcSlip.SlipSound; destEvaporation.ParalyzeTime = srcSlip.ParalyzeTime; destEvaporation.LaunchForwardsMultiplier = srcSlip.LaunchForwardsMultiplier; } } /// /// Raised on an entity to determine if it can slip or not. /// public sealed class SlipAttemptEvent : CancellableEntityEventArgs, IInventoryRelayEvent { public SlotFlags TargetSlots { get; } = SlotFlags.FEET; } [ByRefEvent] public readonly record struct SlipEvent(EntityUid Slipped);