mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-17 05:27:38 +03:00
<!-- This is a semi-strict format, you can add/remove sections as needed but the order/format should be kept the same Remove these comments before submitting --> # Description <!-- Explain this PR in as much detail as applicable Some example prompts to consider: How might this affect the game? The codebase? What might be some alternatives to this? How/Who does this benefit/hurt [the game/codebase]? --> Previously, the "TryStopPull" function didn't make you drop a virtualitem if you were strangling somebody, meaning that you could lose use of a hand until disarmed because it would still consider you to be holding the person. I fixed that, as well as additionally cleaned up the table slam system, which should make it run a bit smoother and be functionally about the same. The "tableable" and "posttabled" components were removed, because they shouldn't have existed in the first place. The only notable non-bugfix change that's player facing is that shoving people who are knocked down on any climbable entity stuns them (as opposed to just tables), but it's a rather minor change and I intend on reworking it pretty heavily once my [other PR](https://github.com/Simple-Station/Einstein-Engines/pull/2199) is reviewed. On the backend, I added an optional variable to "TryClimb" that gives you an option to skip the do-after. If it's possible to change these actions to be predicted, I'd be interested in learning how. --- # TODO <!-- A list of everything you have to do before this PR is "complete" You probably won't have to complete everything before merging but it's good to leave future references --> - [ ] Task - [x] Completed Task --- <!-- This is default collapsed, readers click to expand it and see all your media The PR media section can get very large at times, so this is a good way to keep it clean The title is written using HTML tags The title must be within the <summary> tags or you won't see it --> <details><summary><h1>Media</h1></summary> <p> Note that glass tables do much more damage when slammed into, and even more if they would shatter when attempting to climb it. https://github.com/user-attachments/assets/bc0a12e3-0b67-4d61-aa4e-785e3210c3bb </p> </details> --- # Changelog <!-- You can add an author after the `🆑` to change the name that appears in the changelog (ex: `🆑 Death`) Leaving it blank will default to your GitHub display name This includes all available types for the changelog --> 🆑 - tweak: Table slamming should be more consistent. - fix: You should properly be able to use both hands after letting somebody go from a stranglehold.
386 lines
15 KiB
C#
386 lines
15 KiB
C#
using Content.Shared.ActionBlocker;
|
|
using Content.Shared.Administration.Logs;
|
|
using Content.Shared.Interaction;
|
|
using Content.Shared.Interaction.Events;
|
|
using Content.Shared.Inventory.Events;
|
|
using Content.Shared.Item;
|
|
using Content.Shared.Bed.Sleep;
|
|
using Content.Shared.Database;
|
|
using Content.Shared.Hands;
|
|
using Content.Shared.Mobs;
|
|
using Content.Shared.Mobs.Components;
|
|
using Content.Shared.Movement.Events;
|
|
using Content.Shared.Movement.Systems;
|
|
using Content.Shared.Standing;
|
|
using Content.Shared.Jittering;
|
|
using Content.Shared.Speech.EntitySystems;
|
|
using Content.Shared.StatusEffect;
|
|
using Content.Shared.Throwing;
|
|
using Content.Shared.Whitelist;
|
|
using Robust.Shared.Audio.Systems;
|
|
using Robust.Shared.Containers;
|
|
using Robust.Shared.Physics.Components;
|
|
using Robust.Shared.Physics.Events;
|
|
using Robust.Shared.Physics.Systems;
|
|
using Content.Shared.Actions.Events;
|
|
using Content.Shared.Climbing.Components;
|
|
using Content.Shared._Goobstation.MartialArts.Components;
|
|
|
|
namespace Content.Shared.Stunnable;
|
|
|
|
public abstract class SharedStunSystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
|
|
|
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
|
|
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
|
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
|
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
|
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
|
[Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!;
|
|
[Dependency] private readonly StandingStateSystem _standingState = default!;
|
|
[Dependency] private readonly StatusEffectsSystem _statusEffect = default!;
|
|
[Dependency] private readonly SharedLayingDownSystem _layingDown = default!;
|
|
[Dependency] private readonly SharedContainerSystem _container = default!;
|
|
[Dependency] private readonly SharedStutteringSystem _stutter = default!; // Stun meta
|
|
[Dependency] private readonly SharedJitteringSystem _jitter = default!; // Stun meta
|
|
[Dependency] private readonly ClothingModifyStunTimeSystem _modify = default!; // goob edit
|
|
|
|
/// <summary>
|
|
/// Friction modifier for knocked down players.
|
|
/// Doesn't make them faster but makes them slow down... slower.
|
|
/// </summary>
|
|
public const float KnockDownFrictionModifier = 1f; // WWDP edit
|
|
|
|
public override void Initialize()
|
|
{
|
|
SubscribeLocalEvent<KnockedDownComponent, ComponentInit>(OnKnockInit);
|
|
SubscribeLocalEvent<KnockedDownComponent, ComponentShutdown>(OnKnockShutdown);
|
|
SubscribeLocalEvent<KnockedDownComponent, StandAttemptEvent>(OnStandAttempt);
|
|
SubscribeLocalEvent<KnockedDownComponent, DisarmAttemptEvent>(KnockdownStun);
|
|
|
|
SubscribeLocalEvent<SlowedDownComponent, ComponentInit>(OnSlowInit);
|
|
SubscribeLocalEvent<SlowedDownComponent, ComponentShutdown>(OnSlowRemove);
|
|
|
|
SubscribeLocalEvent<StunnedComponent, ComponentStartup>(UpdateCanMove);
|
|
SubscribeLocalEvent<StunnedComponent, ComponentShutdown>(UpdateCanMove);
|
|
|
|
SubscribeLocalEvent<StunOnContactComponent, ComponentStartup>(OnStunOnContactStartup);
|
|
SubscribeLocalEvent<StunOnContactComponent, StartCollideEvent>(OnStunOnContactCollide);
|
|
|
|
// helping people up if they're knocked down
|
|
SubscribeLocalEvent<KnockedDownComponent, InteractHandEvent>(OnInteractHand);
|
|
SubscribeLocalEvent<SlowedDownComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
|
|
|
|
SubscribeLocalEvent<KnockedDownComponent, TileFrictionEvent>(OnKnockedTileFriction);
|
|
|
|
// Attempt event subscriptions.
|
|
SubscribeLocalEvent<StunnedComponent, ChangeDirectionAttemptEvent>(OnAttempt);
|
|
SubscribeLocalEvent<StunnedComponent, UpdateCanMoveEvent>(OnMoveAttempt);
|
|
SubscribeLocalEvent<StunnedComponent, InteractionAttemptEvent>(OnAttemptInteract);
|
|
SubscribeLocalEvent<StunnedComponent, UseAttemptEvent>(OnAttempt);
|
|
SubscribeLocalEvent<StunnedComponent, ThrowAttemptEvent>(OnAttempt);
|
|
SubscribeLocalEvent<StunnedComponent, DropAttemptEvent>(OnAttempt);
|
|
SubscribeLocalEvent<StunnedComponent, AttackAttemptEvent>(OnAttempt);
|
|
SubscribeLocalEvent<StunnedComponent, PickupAttemptEvent>(OnAttempt);
|
|
SubscribeLocalEvent<StunnedComponent, IsEquippingAttemptEvent>(OnEquipAttempt);
|
|
SubscribeLocalEvent<StunnedComponent, IsUnequippingAttemptEvent>(OnUnequipAttempt);
|
|
SubscribeLocalEvent<MobStateComponent, MobStateChangedEvent>(OnMobStateChanged);
|
|
}
|
|
|
|
private void OnAttemptInteract(Entity<StunnedComponent> ent, ref InteractionAttemptEvent args)
|
|
{
|
|
args.Cancelled = true;
|
|
}
|
|
|
|
private void OnMobStateChanged(EntityUid uid, MobStateComponent component, MobStateChangedEvent args)
|
|
{
|
|
if (!TryComp<StatusEffectsComponent>(uid, out var status))
|
|
return;
|
|
|
|
switch (args.NewMobState)
|
|
{
|
|
case MobState.Alive:
|
|
break;
|
|
case MobState.Critical:
|
|
{
|
|
_statusEffect.TryRemoveStatusEffect(uid, "Stun");
|
|
break;
|
|
}
|
|
case MobState.Dead:
|
|
{
|
|
_statusEffect.TryRemoveStatusEffect(uid, "Stun");
|
|
break;
|
|
}
|
|
case MobState.Invalid:
|
|
default:
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
private void UpdateCanMove(EntityUid uid, StunnedComponent component, EntityEventArgs args)
|
|
{
|
|
_blocker.UpdateCanMove(uid);
|
|
}
|
|
|
|
private void OnStunOnContactStartup(Entity<StunOnContactComponent> ent, ref ComponentStartup args)
|
|
{
|
|
if (TryComp<PhysicsComponent>(ent, out var body))
|
|
_broadphase.RegenerateContacts(ent, body);
|
|
}
|
|
|
|
private void OnStunOnContactCollide(Entity<StunOnContactComponent> ent, ref StartCollideEvent args)
|
|
{
|
|
if (args.OurFixtureId != ent.Comp.FixtureId)
|
|
return;
|
|
|
|
if (_entityWhitelist.IsBlacklistPass(ent.Comp.Blacklist, args.OtherEntity))
|
|
return;
|
|
|
|
if (!TryComp<StatusEffectsComponent>(args.OtherEntity, out var status))
|
|
return;
|
|
|
|
TryStun(args.OtherEntity, ent.Comp.Duration, true, status);
|
|
TryKnockdown(args.OtherEntity, ent.Comp.Duration, true, status);
|
|
}
|
|
|
|
private void OnKnockInit(EntityUid uid, KnockedDownComponent component, ComponentInit args)
|
|
{
|
|
RaiseNetworkEvent(new CheckAutoGetUpEvent(GetNetEntity(uid)));
|
|
_layingDown.TryLieDown(uid, null, component.DropHeldItemsBehavior); // WD EDIT
|
|
}
|
|
|
|
private void OnKnockShutdown(EntityUid uid, KnockedDownComponent component, ComponentShutdown args)
|
|
{
|
|
if (!TryComp(uid, out StandingStateComponent? standing))
|
|
return;
|
|
|
|
if (TryComp(uid, out LayingDownComponent? layingDown))
|
|
{
|
|
if (layingDown.AutoGetUp && !_container.IsEntityInContainer(uid))
|
|
_layingDown.TryStandUp(uid, layingDown);
|
|
return;
|
|
}
|
|
|
|
_standingState.Stand(uid, standing);
|
|
}
|
|
|
|
private void OnStandAttempt(EntityUid uid, KnockedDownComponent component, StandAttemptEvent args)
|
|
{
|
|
if (component.LifeStage <= ComponentLifeStage.Running)
|
|
args.Cancel();
|
|
component.FollowUp = false;
|
|
}
|
|
|
|
private void OnSlowInit(EntityUid uid, SlowedDownComponent component, ComponentInit args)
|
|
{
|
|
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
|
}
|
|
|
|
private void OnSlowRemove(EntityUid uid, SlowedDownComponent component, ComponentShutdown args)
|
|
{
|
|
component.SprintSpeedModifier = 1f;
|
|
component.WalkSpeedModifier = 1f;
|
|
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
|
}
|
|
|
|
private void OnRefreshMovespeed(EntityUid uid, SlowedDownComponent component, RefreshMovementSpeedModifiersEvent args)
|
|
{
|
|
args.ModifySpeed(component.WalkSpeedModifier, component.SprintSpeedModifier);
|
|
}
|
|
|
|
// TODO STUN: Make events for different things. (Getting modifiers, attempt events, informative events...)
|
|
|
|
/// <summary>
|
|
/// Stuns the entity, disallowing it from doing many interactions temporarily.
|
|
/// </summary>
|
|
public bool TryStun(EntityUid uid, TimeSpan time, bool refresh,
|
|
StatusEffectsComponent? status = null)
|
|
{
|
|
time *= _modify.GetModifier(uid); // Goobstation
|
|
|
|
if (time <= TimeSpan.Zero
|
|
|| !Resolve(uid, ref status, false)
|
|
|| !_statusEffect.TryAddStatusEffect<StunnedComponent>(uid, "Stun", time, refresh))
|
|
return false;
|
|
|
|
// goob edit
|
|
_jitter.DoJitter(uid, time, refresh);
|
|
_stutter.DoStutter(uid, time, refresh);
|
|
// goob edit end
|
|
|
|
var ev = new StunnedEvent();
|
|
RaiseLocalEvent(uid, ref ev);
|
|
|
|
_adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} stunned for {time.Seconds} seconds");
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Knocks down the entity, making it fall to the ground.
|
|
/// </summary>
|
|
public bool TryKnockdown(EntityUid uid, TimeSpan time, bool refresh, DropHeldItemsBehavior behavior,
|
|
StatusEffectsComponent? status = null, float frictionMultiplier = KnockDownFrictionModifier) // WWDP slowdown moved to StandingStateComponent/LayingFrictionMultiplier
|
|
{
|
|
time *= _modify.GetModifier(uid); // Goobstation
|
|
|
|
if (time <= TimeSpan.Zero || !Resolve(uid, ref status, false))
|
|
return false;
|
|
|
|
var component = _componentFactory.GetComponent<KnockedDownComponent>();
|
|
component.DropHeldItemsBehavior = behavior;
|
|
component.FrictionMultiplier = frictionMultiplier; // WWDP
|
|
if (!_statusEffect.TryAddStatusEffect(uid, "KnockedDown", time, refresh, component))
|
|
return false;
|
|
|
|
var ev = new KnockedDownEvent();
|
|
RaiseLocalEvent(uid, ref ev);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Knocks down the entity, making it fall to the ground.
|
|
/// </summary>
|
|
public bool TryKnockdown(EntityUid uid, TimeSpan time, bool refresh,
|
|
StatusEffectsComponent? status = null, float frictionMultiplier = KnockDownFrictionModifier) // WWDP
|
|
{
|
|
// WWDP edit start
|
|
time *= _modify.GetModifier(uid); // Goobstation
|
|
|
|
if (time <= TimeSpan.Zero || !Resolve(uid, ref status, false))
|
|
return false;
|
|
|
|
var component = _componentFactory.GetComponent<KnockedDownComponent>();
|
|
component.FrictionMultiplier = frictionMultiplier; // WWDP
|
|
|
|
if (!_statusEffect.TryAddStatusEffect(uid, "KnockedDown", time, refresh, component))
|
|
return false;
|
|
// WWDP edit end
|
|
|
|
var ev = new KnockedDownEvent();
|
|
RaiseLocalEvent(uid, ref ev);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies knockdown and stun to the entity temporarily.
|
|
/// </summary>
|
|
public bool TryParalyze(EntityUid uid, TimeSpan time, bool refresh,
|
|
StatusEffectsComponent? status = null, float frictionMultiplier = KnockDownFrictionModifier) // WWDP
|
|
{
|
|
if (!Resolve(uid, ref status, false))
|
|
return false;
|
|
|
|
return TryKnockdown(uid, time, refresh, status, frictionMultiplier) && TryStun(uid, time, refresh, status); // WWDP
|
|
}
|
|
|
|
/// <summary>
|
|
/// Slows down the mob's walking/running speed temporarily
|
|
/// </summary>
|
|
public bool TrySlowdown(EntityUid uid, TimeSpan time, bool refresh,
|
|
float walkSpeedMultiplier = 1f, float runSpeedMultiplier = 1f,
|
|
StatusEffectsComponent? status = null)
|
|
{
|
|
if (!Resolve(uid, ref status, false)
|
|
|| time <= TimeSpan.Zero)
|
|
return false;
|
|
|
|
if (_statusEffect.TryAddStatusEffect<SlowedDownComponent>(uid, "SlowedDown", time, refresh, status))
|
|
{
|
|
var slowed = Comp<SlowedDownComponent>(uid);
|
|
// Doesn't make much sense to have the "TrySlowdown" method speed up entities now does it?
|
|
walkSpeedMultiplier = Math.Clamp(walkSpeedMultiplier, 0f, 1f);
|
|
runSpeedMultiplier = Math.Clamp(runSpeedMultiplier, 0f, 1f);
|
|
|
|
slowed.WalkSpeedModifier *= walkSpeedMultiplier;
|
|
slowed.SprintSpeedModifier *= runSpeedMultiplier;
|
|
|
|
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void OnInteractHand(EntityUid uid, KnockedDownComponent knocked, InteractHandEvent args)
|
|
{
|
|
if (args.Handled || knocked.HelpTimer > 0f
|
|
|| HasComp<SleepingComponent>(uid))
|
|
return;
|
|
|
|
// Set it to half the help interval so helping is actually useful...
|
|
knocked.HelpTimer = knocked.HelpInterval / 2f;
|
|
|
|
_statusEffect.TryRemoveTime(uid, "KnockedDown", TimeSpan.FromSeconds(knocked.HelpInterval));
|
|
_audio.PlayPredicted(knocked.StunAttemptSound, uid, args.User);
|
|
Dirty(uid, knocked);
|
|
|
|
args.Handled = true;
|
|
}
|
|
|
|
private void OnKnockedTileFriction(EntityUid uid, KnockedDownComponent component, ref TileFrictionEvent args)
|
|
{
|
|
args.Modifier *= component.FrictionMultiplier; // WWDP
|
|
}
|
|
|
|
// should make it so that one time when somebody gets knocked over, you can push them for a short stun.
|
|
// On the slate for a rework once I make combos eat inputs, but that's not my goal right now.
|
|
private void KnockdownStun(Entity<KnockedDownComponent> ent, ref DisarmAttemptEvent args)
|
|
{
|
|
if (ent.Comp.FollowUp || !TryComp<ClimbingComponent>(ent, out var component) || !component.IsClimbing)
|
|
return;
|
|
TryParalyze(ent, TimeSpan.FromSeconds(1.5f), false);
|
|
ent.Comp.FollowUp = true;
|
|
}
|
|
|
|
#region Attempt Event Handling
|
|
|
|
private void OnMoveAttempt(EntityUid uid, StunnedComponent stunned, UpdateCanMoveEvent args)
|
|
{
|
|
if (stunned.LifeStage > ComponentLifeStage.Running)
|
|
return;
|
|
|
|
args.Cancel();
|
|
}
|
|
|
|
private void OnAttempt(EntityUid uid, StunnedComponent stunned, CancellableEntityEventArgs args)
|
|
{
|
|
args.Cancel();
|
|
}
|
|
|
|
private void OnEquipAttempt(EntityUid uid, StunnedComponent stunned, IsEquippingAttemptEvent args)
|
|
{
|
|
// is this a self-equip, or are they being stripped?
|
|
if (args.Equipee != uid)
|
|
return;
|
|
|
|
args.Cancel();
|
|
}
|
|
|
|
private void OnUnequipAttempt(EntityUid uid, StunnedComponent stunned, IsUnequippingAttemptEvent args)
|
|
{
|
|
// is this a self-equip, or are they being stripped?
|
|
if (args.Unequipee != uid)
|
|
return;
|
|
|
|
args.Cancel();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raised directed on an entity when it is stunned.
|
|
/// </summary>
|
|
[ByRefEvent]
|
|
public record struct StunnedEvent;
|
|
|
|
/// <summary>
|
|
/// Raised directed on an entity when it is knocked down.
|
|
/// </summary>
|
|
[ByRefEvent]
|
|
public record struct KnockedDownEvent;
|