diff --git a/Content.Shared/Bed/Sleep/SleepingSystem.cs b/Content.Shared/Bed/Sleep/SleepingSystem.cs index 648d1b4010..0e29fcd98a 100644 --- a/Content.Shared/Bed/Sleep/SleepingSystem.cs +++ b/Content.Shared/Bed/Sleep/SleepingSystem.cs @@ -158,7 +158,7 @@ public sealed partial class SleepingSystem : EntitySystem private void OnSlip(Entity ent, ref SlipAttemptEvent args) { - args.Cancel(); + args.NoSlip = true; } private void OnConsciousAttempt(Entity ent, ref ConsciousAttemptEvent args) diff --git a/Content.Shared/Clothing/Components/SpeedModifierContactCapClothingComponent.cs b/Content.Shared/Clothing/Components/SpeedModifierContactCapClothingComponent.cs new file mode 100644 index 0000000000..f23f541278 --- /dev/null +++ b/Content.Shared/Clothing/Components/SpeedModifierContactCapClothingComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Clothing.EntitySystems; +using Robust.Shared.GameStates; + +namespace Content.Shared.Clothing.Components; + +/// +/// When equipped, sets a max cap to the slowdown applied from contact speed modifiers. (E.g. glue puddles, kudzu). +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SpeedModifierContactCapClothingSystem))] +public sealed partial class SpeedModifierContactCapClothingComponent : Component +{ + [DataField, AutoNetworkedField] + public float MaxContactSprintSlowdown = 1f; + + [DataField, AutoNetworkedField] + public float MaxContactWalkSlowdown = 1f; +} diff --git a/Content.Shared/Clothing/EntitySystems/SpeedModifierContactCapClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/SpeedModifierContactCapClothingSystem.cs new file mode 100644 index 0000000000..4b56b05167 --- /dev/null +++ b/Content.Shared/Clothing/EntitySystems/SpeedModifierContactCapClothingSystem.cs @@ -0,0 +1,20 @@ +using Content.Shared.Clothing.Components; +using Content.Shared.Inventory; +using Content.Shared.Movement.Events; + +namespace Content.Shared.Clothing.EntitySystems; + +public sealed class SpeedModifierContactCapClothingSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(OnGetMaxSlow); + } + + private void OnGetMaxSlow(Entity ent, ref InventoryRelayedEvent args) + { + args.Args.SetIfMax(ent.Comp.MaxContactSprintSlowdown, ent.Comp.MaxContactWalkSlowdown); + } +} diff --git a/Content.Shared/Damage/Systems/SharedGodmodeSystem.cs b/Content.Shared/Damage/Systems/SharedGodmodeSystem.cs index d904c211ee..20e29ef434 100644 --- a/Content.Shared/Damage/Systems/SharedGodmodeSystem.cs +++ b/Content.Shared/Damage/Systems/SharedGodmodeSystem.cs @@ -21,7 +21,7 @@ public abstract class SharedGodmodeSystem : EntitySystem private void OnSlipAttempt(EntityUid uid, GodmodeComponent component, SlipAttemptEvent args) { - args.Cancel(); + args.NoSlip = true; } private void OnBeforeDamageChanged(EntityUid uid, GodmodeComponent component, ref BeforeDamageChangedEvent args) diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index f0bb73c192..fc300b24af 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -6,6 +6,7 @@ using Content.Shared.Eye.Blinding.Systems; using Content.Shared.Gravity; using Content.Shared.IdentityManagement.Components; using Content.Shared.Inventory.Events; +using Content.Shared.Movement.Events; using Content.Shared.Movement.Systems; using Content.Shared.NameModifier.EntitySystems; using Content.Shared.Overlays; @@ -34,6 +35,8 @@ public partial class InventorySystem // by-ref events SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); + SubscribeLocalEvent(RefRelayInventoryEvent); + SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); // Eye/vision events diff --git a/Content.Shared/Movement/Events/GetSpeedModifierContactCapEvent.cs b/Content.Shared/Movement/Events/GetSpeedModifierContactCapEvent.cs new file mode 100644 index 0000000000..b72fe7d18a --- /dev/null +++ b/Content.Shared/Movement/Events/GetSpeedModifierContactCapEvent.cs @@ -0,0 +1,22 @@ +using Content.Shared.Inventory; + +namespace Content.Shared.Movement.Events; + +/// +/// Raised on an entity to check if it has a max contact slowdown. +/// +[ByRefEvent] +public record struct GetSpeedModifierContactCapEvent() : IInventoryRelayEvent +{ + SlotFlags IInventoryRelayEvent.TargetSlots => ~SlotFlags.POCKET; + + public float MaxSprintSlowdown = 0f; + + public float MaxWalkSlowdown = 0f; + + public void SetIfMax(float valueSprint, float valueWalk) + { + MaxSprintSlowdown = MathF.Max(MaxSprintSlowdown, valueSprint); + MaxWalkSlowdown = MathF.Max(MaxWalkSlowdown, valueWalk); + } +} diff --git a/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs b/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs index 6e1b3a29ae..089fbbf924 100644 --- a/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs +++ b/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs @@ -1,4 +1,7 @@ +using Content.Shared.Inventory; using Content.Shared.Movement.Components; +using Content.Shared.Movement.Events; +using Content.Shared.Slippery; using Content.Shared.Whitelist; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; @@ -85,16 +88,37 @@ public sealed class SpeedModifierContactsSystem : EntitySystem var entries = 0; foreach (var ent in _physics.GetContactingEntities(uid, physicsComponent)) { - if (!TryComp(ent, out var slowContactsComponent)) - continue; + bool speedModified = false; - if (_whitelistSystem.IsWhitelistPass(slowContactsComponent.IgnoreWhitelist, uid)) - continue; + if (TryComp(ent, out var slowContactsComponent)) + { + if (_whitelistSystem.IsWhitelistPass(slowContactsComponent.IgnoreWhitelist, uid)) + continue; - walkSpeed += slowContactsComponent.WalkSpeedModifier; - sprintSpeed += slowContactsComponent.SprintSpeedModifier; - remove = false; - entries++; + walkSpeed += slowContactsComponent.WalkSpeedModifier; + sprintSpeed += slowContactsComponent.SprintSpeedModifier; + speedModified = true; + } + + // SpeedModifierContactsComponent takes priority over SlowedOverSlipperyComponent, effectively overriding the slippery slow. + if (TryComp(ent, out var slipperyComponent) && speedModified == false) + { + var evSlippery = new GetSlowedOverSlipperyModifierEvent(); + RaiseLocalEvent(uid, ref evSlippery); + + if (evSlippery.SlowdownModifier != 1) + { + walkSpeed += evSlippery.SlowdownModifier; + sprintSpeed += evSlippery.SlowdownModifier; + speedModified = true; + } + } + + if (speedModified) + { + remove = false; + entries++; + } } if (entries > 0) @@ -102,6 +126,12 @@ public sealed class SpeedModifierContactsSystem : EntitySystem walkSpeed /= entries; sprintSpeed /= entries; + var evMax = new GetSpeedModifierContactCapEvent(); + RaiseLocalEvent(uid, ref evMax); + + walkSpeed = MathF.Max(walkSpeed, evMax.MaxWalkSlowdown); + sprintSpeed = MathF.Max(sprintSpeed, evMax.MaxSprintSlowdown); + args.ModifySpeed(walkSpeed, sprintSpeed); } @@ -118,11 +148,19 @@ public sealed class SpeedModifierContactsSystem : EntitySystem private void OnEntityEnter(EntityUid uid, SpeedModifierContactsComponent component, ref StartCollideEvent args) { - var otherUid = args.OtherEntity; - if (!HasComp(otherUid)) + AddModifiedEntity(args.OtherEntity); + } + + /// + /// Add an entity to be checked for speed modification from contact with another entity. + /// + /// The entity to be added. + public void AddModifiedEntity(EntityUid uid) + { + if (!HasComp(uid)) return; - EnsureComp(otherUid); - _toUpdate.Add(otherUid); + EnsureComp(uid); + _toUpdate.Add(uid); } } diff --git a/Content.Shared/Slippery/GetSlowedOverSlipperyModifierEvent.cs b/Content.Shared/Slippery/GetSlowedOverSlipperyModifierEvent.cs new file mode 100644 index 0000000000..4036c6e155 --- /dev/null +++ b/Content.Shared/Slippery/GetSlowedOverSlipperyModifierEvent.cs @@ -0,0 +1,10 @@ +using Content.Shared.Inventory; + +namespace Content.Shared.Slippery; +[ByRefEvent] +public record struct GetSlowedOverSlipperyModifierEvent() : IInventoryRelayEvent +{ + SlotFlags IInventoryRelayEvent.TargetSlots => ~SlotFlags.POCKET; + + public float SlowdownModifier = 1f; +} diff --git a/Content.Shared/Slippery/SlipperySystem.cs b/Content.Shared/Slippery/SlipperySystem.cs index 5b2a2dfe45..19cc19aa19 100644 --- a/Content.Shared/Slippery/SlipperySystem.cs +++ b/Content.Shared/Slippery/SlipperySystem.cs @@ -2,6 +2,8 @@ using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Inventory; using Robust.Shared.Network; +using Content.Shared.Movement.Components; +using Content.Shared.Movement.Systems; using Content.Shared.Popups; using Content.Shared.StatusEffect; using Content.Shared.StepTrigger.Systems; @@ -12,11 +14,12 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; +using Robust.Shared.Physics.Events; using Robust.Shared.Utility; namespace Content.Shared.Slippery; -[UsedImplicitly] +[UsedImplicitly] public sealed class SlipperySystem : EntitySystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; @@ -25,6 +28,7 @@ public sealed class SlipperySystem : EntitySystem [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SpeedModifierContactsSystem _speedModifier = default!; public override void Initialize() { @@ -33,9 +37,13 @@ public sealed class SlipperySystem : EntitySystem SubscribeLocalEvent(HandleAttemptCollide); SubscribeLocalEvent(HandleStepTrigger); SubscribeLocalEvent(OnNoSlipAttempt); + SubscribeLocalEvent(OnSlowedOverSlipAttempt); SubscribeLocalEvent(OnThrownSlipAttempt); // 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>((e, c, ev) => OnSlowedOverSlipAttempt(e, c, ev.Args)); + SubscribeLocalEvent>(OnGetSlowedOverSlipperyModifier); + SubscribeLocalEvent(OnEntityExit); } private void HandleStepTrigger(EntityUid uid, SlipperyComponent component, ref StepTriggeredOffEvent args) @@ -53,7 +61,12 @@ public sealed class SlipperySystem : EntitySystem private static void OnNoSlipAttempt(EntityUid uid, NoSlipComponent component, SlipAttemptEvent args) { - args.Cancel(); + args.NoSlip = true; + } + + private void OnSlowedOverSlipAttempt(EntityUid uid, SlowedOverSlipperyComponent component, SlipAttemptEvent args) + { + args.SlowOverSlippery = true; } private void OnThrownSlipAttempt(EntityUid uid, ThrownItemComponent comp, ref SlipCausingAttemptEvent args) @@ -61,6 +74,17 @@ public sealed class SlipperySystem : EntitySystem args.Cancelled = true; } + private void OnGetSlowedOverSlipperyModifier(EntityUid uid, SlowedOverSlipperyComponent comp, ref InventoryRelayedEvent args) + { + args.Args.SlowdownModifier *= comp.SlowdownModifier; + } + + private void OnEntityExit(EntityUid uid, SlipperyComponent component, ref EndCollideEvent args) + { + if (HasComp(args.OtherEntity)) + _speedModifier.AddModifiedEntity(args.OtherEntity); + } + private bool CanSlip(EntityUid uid, EntityUid toSlip) { return !_container.IsEntityInContainer(uid) @@ -74,7 +98,10 @@ public sealed class SlipperySystem : EntitySystem var attemptEv = new SlipAttemptEvent(); RaiseLocalEvent(other, attemptEv); - if (attemptEv.Cancelled) + if (attemptEv.SlowOverSlippery) + _speedModifier.AddModifiedEntity(other); + + if (attemptEv.NoSlip) return; var attemptCausingEv = new SlipCausingAttemptEvent(); @@ -115,8 +142,12 @@ public sealed class SlipperySystem : EntitySystem /// /// Raised on an entity to determine if it can slip or not. /// -public sealed class SlipAttemptEvent : CancellableEntityEventArgs, IInventoryRelayEvent +public sealed class SlipAttemptEvent : EntityEventArgs, IInventoryRelayEvent { + public bool NoSlip; + + public bool SlowOverSlippery; + public SlotFlags TargetSlots { get; } = SlotFlags.FEET; } diff --git a/Content.Shared/Slippery/SlowedOverSlipperyComponent.cs b/Content.Shared/Slippery/SlowedOverSlipperyComponent.cs new file mode 100644 index 0000000000..1a2638bbef --- /dev/null +++ b/Content.Shared/Slippery/SlowedOverSlipperyComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Slippery; + +/// +/// Slows down the user when passing over an entity with . Does not prevent slipping, see . +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SlipperySystem))] +public sealed partial class SlowedOverSlipperyComponent : Component +{ + [DataField(required: true), AutoNetworkedField] + public float SlowdownModifier = 1f; +} diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml index c8a3e615f3..bd37135e98 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml @@ -109,6 +109,11 @@ - type: Clothing sprite: Clothing/Shoes/Specific/galoshes.rsi - type: NoSlip + - type: SlowedOverSlippery + slowdownModifier: 0.7 + - type: SpeedModifierContactCapClothing + maxContactSprintSlowdown: 0.7 + maxContactWalkSlowdown: 0.7 - type: entity parent: [ClothingShoesBaseButcherable, BaseMajorContraband]