Files
wwdpublic/Content.Shared/Slippery/SlipperySystem.cs
Skubman 26118e8994 Parkour Training Rework (#1432)
# Description

Parkour Training, Sluggish and Snail-Paced have been reworked to be more
impactful in everyday space life. A new trait has been added, Bad Knees.

**Parkour Training** (-5 points)
- No longer modifies laying down
- Climbing speed bonus reduced from 65% to 50%
- 30% shorter slip stuns
- 50% resistance against slows in difficult terrain
- Example: Spider webs inflict a 50% slow, reduced by 50% results in a
25% slow.

Notably, the reworked Parkour Training is much closer to the CDDA trait
[Parkour
Expert](https://cddawiki.danmakudan.com/wiki/index.php/Parkour_Expert).

Difficult terrain examples: Spider webs, slime/blood puddles, kudzu,
space glue

**(NEW)** **Bad Knees** (3 points)
- Designed to be the opposite of Parkour Training
- 50% climbing speed penalty
- 40% longer slip stuns
- 35% increased slows in difficult terrain
- Example: Spider webs inflict a 50% slow, increased by 35% results in a
67.5% slow.

Inspired by the CDDA trait of the same name [Bad
Knees](https://cddawiki.danmakudan.com/wiki/index.php/Bad_Knees).

The stun time for banana peels and puddles is 3 seconds. With Bad Knees,
the stun is increased to 4.2 seconds. The time it takes to handcuff
someone is 3.5 seconds. Go figure :trollface:

**Sluggish** (3 points)
- No longer increases the time for laying down and climbing tables
(given to Bad Knees instead)
- Speed penalty increased from 15% to 16%

**Snail-Paced** (5 points)
- No longer increases the time for laying down and climbing tables
- Speed penalty increased from 30% to 32%
- No longer prevents you from slipping when running due to how slow your
sprint speed is. Snail-Paced trait users must suffer.

## Media

**Parkour Training**

![image](https://github.com/user-attachments/assets/b320d18a-7ac8-419d-b783-28a4d3ac0bb5)

**Bad Knees**

![image](https://github.com/user-attachments/assets/caa64d6a-a58b-4378-a947-0a5f3f6e70a6)

**Sluggish**

![image](https://github.com/user-attachments/assets/7f9ac71f-475f-4aa9-8e36-b93aaad12670)

**Snail-Paced**

![image](https://github.com/user-attachments/assets/faf70f4b-2911-4d03-a72a-99972838b7f9)

## Changelog

🆑 Skubman
- tweak: The Parkour Training trait has been reworked. It no longer
makes you lie down or get up faster, but grants you 30% shorter slip
stuns and 50% resistance against slows in difficult terrain.
- tweak: The Sluggish and Snail-Paced traits now only reduce your
movement speed with no other effects. The speed reductions have been
slightly increased from 15% to 16% for Sluggish, and 30% to 32% for
Snail-Paced.
- add: Added a new 3-point negative trait called Bad Knees. It is the
opposite of Parkour Training and it makes you climb tables slower,
stunned for longer against slip stuns, and move more slowly in difficult
terrain.
- tweak: Snail-Paced no longer prevents you from slipping when running.

(cherry picked from commit 89354922a0f6fb55111e0ce2b4b19e5f6d6ea0e2)
2025-01-14 01:32:29 +03:00

162 lines
5.9 KiB
C#

using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Inventory;
using Robust.Shared.Network;
using Content.Shared.Popups;
using Content.Shared.StatusEffect;
using Content.Shared.StepTrigger.Systems;
using Content.Shared.Stunnable;
using Content.Shared.Throwing;
using Content.Shared.Mood;
using JetBrains.Annotations;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Utility;
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<SlipperyComponent, StepTriggerAttemptEvent>(HandleAttemptCollide);
SubscribeLocalEvent<SlipperyComponent, StepTriggeredOffEvent>(HandleStepTrigger);
SubscribeLocalEvent<NoSlipComponent, SlipAttemptEvent>(OnNoSlipAttempt);
SubscribeLocalEvent<ThrownItemComponent, SlipCausingAttemptEvent>(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<NoSlipComponent, InventoryRelayedEvent<SlipAttemptEvent>>((e, c, ev) => OnNoSlipAttempt(e, c, ev.Args));
SubscribeLocalEvent<SlippableModifierComponent, SlippedEvent>(OnSlippableModifierSlipped);
}
private void HandleStepTrigger(EntityUid uid, SlipperyComponent component, ref StepTriggeredOffEvent 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 void OnThrownSlipAttempt(EntityUid uid, ThrownItemComponent comp, ref SlipCausingAttemptEvent args)
{
args.Cancelled = true;
}
private void OnSlippableModifierSlipped(EntityUid uid, SlippableModifierComponent comp, ref SlippedEvent args)
{
args.ParalyzeTime *= comp.ParalyzeTimeMultiplier;
}
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<KnockedDownComponent>(other) && !component.SuperSlippery)
return;
var attemptEv = new SlipAttemptEvent();
RaiseLocalEvent(other, attemptEv);
if (attemptEv.Cancelled)
return;
var attemptCausingEv = new SlipCausingAttemptEvent();
RaiseLocalEvent(uid, ref attemptCausingEv);
if (attemptCausingEv.Cancelled)
return;
var ev = new SlipEvent(other);
RaiseLocalEvent(uid, ref ev);
var slippedEv = new SlippedEvent(uid, component.ParalyzeTime);
RaiseLocalEvent(other, ref slippedEv);
if (TryComp(other, out PhysicsComponent? physics) && !HasComp<SlidingComponent>(other))
{
_physics.SetLinearVelocity(other, physics.LinearVelocity * component.LaunchForwardsMultiplier, body: physics);
if (component.SuperSlippery)
{
var sliding = EnsureComp<SlidingComponent>(other);
sliding.CollidingEntities.Add(uid);
DebugTools.Assert(_physics.GetContactingEntities(other, physics).Contains(uid));
}
}
var playSound = !_statusEffects.HasStatusEffect(other, "KnockedDown");
_stun.TryParalyze(other, TimeSpan.FromSeconds(slippedEv.ParalyzeTime), true);
RaiseLocalEvent(other, new MoodEffectEvent("MobSlipped"));
// 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}");
}
}
/// <summary>
/// Raised on an entity to determine if it can slip or not.
/// </summary>
public sealed class SlipAttemptEvent : CancellableEntityEventArgs, IInventoryRelayEvent
{
public SlotFlags TargetSlots { get; } = SlotFlags.FEET;
}
/// <summary>
/// Raised on an entity that is causing the slip event (e.g, the banana peel), to determine if the slip attempt should be cancelled.
/// </summary>
/// <param name="Cancelled">If the slip should be cancelled</param>
[ByRefEvent]
public record struct SlipCausingAttemptEvent (bool Cancelled);
/// Raised on an entity that CAUSED some other entity to slip (e.g., the banana peel).
/// <param name="Slipped">The entity being slipped</param>
[ByRefEvent]
public readonly record struct SlipEvent(EntityUid Slipped);
/// Raised on an entity that slipped.
/// <param name="Slipper">The entity that caused the slip</param>
/// <param name="ParalyzeTime">How many seconds the entity will be paralyzed for, modifiable with this event.</param>
[ByRefEvent]
public record struct SlippedEvent
{
public readonly EntityUid Slipper;
public float ParalyzeTime;
public SlippedEvent(EntityUid slipper, float paralyzeTime)
{
Slipper = slipper;
ParalyzeTime = paralyzeTime;
}
}