using System.Numerics; using Content.Shared._Sunrise.Jump; using Content.Shared._Sunrise.SunriseCCVars; using Content.Shared.ActionBlocker; using Content.Shared.Buckle; using Content.Shared.Buckle.Components; using Content.Shared.Chat; using Content.Shared.Chat.Prototypes; using Content.Shared.Damage.Systems; using Content.Shared.DoAfter; using Content.Shared.Emoting; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Mobs; using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Events; using Content.Shared.Movement.Systems; using Content.Shared.Physics; using Content.Shared.Rotation; using Content.Shared.StatusEffect; using Content.Shared.Stunnable; using Content.Shared.Throwing; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.Network; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; using Robust.Shared.Random; namespace Content.Shared.Standing; public abstract class SharedStandingStateSystem : EntitySystem { [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; [Dependency] private readonly SharedRotationVisualsSystem _rotation = default!; [Dependency] private readonly SharedBuckleSystem _buckle = default!; [Dependency] private readonly ThrowingSystem _throwing = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; [Dependency] private readonly ActionBlockerSystem _blocker = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [ValidatePrototypeId] private const string FallStatusEffectKey = "Fall"; [ValidatePrototypeId] private const string EmoteFallOnNeckProto = "FallOnNeck"; private static float _fallDeadChance; public const float FallModifier = 0.4f; private const int StandingCollisionLayer = (int) CollisionGroup.LowImpassable; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnStandUpDoAfter); SubscribeLocalEvent(OnDownDoAfter); SubscribeLocalEvent(OnMove); SubscribeLocalEvent(OnRefreshMovementSpeed); SubscribeLocalEvent(OnMobCollide); SubscribeLocalEvent(OnMobTargetCollide); SubscribeLocalEvent(FallOver); SubscribeLocalEvent(OnFallTileFriction); SubscribeLocalEvent(OnMoveAttempt); SubscribeLocalEvent(UpdateCanMove); SubscribeLocalEvent(UpdateCanMove); _cfg.OnValueChanged(SunriseCCVars.FallDeadChance, OnFallDeadChanceChanged, true); } private void OnFallDeadChanceChanged(float value) { _fallDeadChance = value; } private void UpdateCanMove(EntityUid uid, FallComponent component, EntityEventArgs args) { _blocker.UpdateCanMove(uid); } private void OnMoveAttempt(EntityUid uid, FallComponent component, UpdateCanMoveEvent args) { if (component.LifeStage > ComponentLifeStage.Running) return; args.Cancel(); } private void OnFallTileFriction(EntityUid uid, FallComponent component, ref TileFrictionEvent args) { args.Modifier *= FallModifier; } private void FallOver(EntityUid uid, StandingStateComponent component, DropHandItemsEvent args) { var direction = EntityManager.TryGetComponent(uid, out PhysicsComponent? comp) ? comp.LinearVelocity / 50 : Vector2.Zero; var dropAngle = _random.NextFloat(0.8f, 1.2f); if (!TryComp(uid, out HandsComponent? handsComp)) return; var worldRotation = _transform.GetWorldRotation(uid).ToVec(); foreach (var hand in handsComp.Hands.Values) { if (hand.HeldEntity is not { } held) continue; if (!_handsSystem.TryDrop(uid, hand, checkActionBlocker: false, handsComp: handsComp)) continue; _throwing.TryThrow( held, _random.NextAngle().RotateVec(direction / dropAngle + worldRotation / 50), 0.5f * dropAngle * _random.NextFloat(-0.9f, 1.1f), uid, 0); } } private void OnMobTargetCollide(Entity ent, ref AttemptMobTargetCollideEvent args) { if (ent.Comp.CurrentState == StandingState.Laying) { args.Cancelled = true; } } private void OnMobCollide(Entity ent, ref AttemptMobCollideEvent args) { if (ent.Comp.CurrentState == StandingState.Laying) { args.Cancelled = true; } } #region Implementation private void OnStandUpDoAfter(EntityUid uid, StandingStateComponent component, StandUpDoAfterEvent args) { if (args.Handled || args.Cancelled) return; if (_mobState.IsIncapacitated(uid)) return; Stand(uid); args.Handled = true; } private void OnDownDoAfter(EntityUid uid, StandingStateComponent component, DownDoAfterEvent args) { if (args.Handled || args.Cancelled) return; Down(uid, dropHeldItems: false); args.Handled = true; } private void OnMove(Entity ent, ref MoveEvent args) { if (IsStanding(ent)) return; _movement.RefreshMovementSpeedModifiers(ent); } private void OnRefreshMovementSpeed(EntityUid uid, StandingStateComponent component, RefreshMovementSpeedModifiersEvent args) { if (IsDown(uid)) { var baseSpeedModify = component.BaseSpeedModify; if (!TryComp(uid, out var handsComponent) || handsComponent.Hands.Count == 0) { args.ModifySpeed(baseSpeedModify, baseSpeedModify); return; } var totalHands = handsComponent.Hands.Count; var freeHands = handsComponent.CountFreeHands(); var occupiedHandsRatio = (float)(totalHands - freeHands) / totalHands; var speedModifier = baseSpeedModify * (1 - occupiedHandsRatio * 0.5f); args.ModifySpeed(speedModifier, speedModifier); } else { args.ModifySpeed(1f, 1f); } } public bool TryStandUp(EntityUid uid, StandingStateComponent? standingState = null) { if (!Resolve(uid, ref standingState, false) || standingState.CurrentState is not StandingState.Laying || _mobState.IsIncapacitated(uid) || HasComp(uid) || TerminatingOrDeleted(uid)) return false; var args = new DoAfterArgs(EntityManager, uid, standingState.CycleTime, new StandUpDoAfterEvent(), uid) { BreakOnHandChange = false, RequireCanInteract = false, BreakOnDamage = false, BreakOnMove = false, }; return _doAfter.TryStartDoAfter(args); } public bool TryLieDown(EntityUid uid, StandingStateComponent? standingState = null) { if (!Resolve(uid, ref standingState, false) || standingState.CurrentState is not StandingState.Standing || !_mobState.IsAlive(uid) || TerminatingOrDeleted(uid)) return false; var args = new DoAfterArgs(EntityManager, uid, standingState.CycleTime, new DownDoAfterEvent(), uid) { BreakOnHandChange = false, RequireCanInteract = false, BreakOnMove = true, BreakOnDamage = true, }; return _doAfter.TryStartDoAfter(args); } public void Fall(EntityUid uid) { if (!TryComp(uid, out var physics) || HasComp(uid)) return; var velocity = physics.LinearVelocity; if (velocity.LengthSquared() < 0.1f) { Down(uid, dropHeldItems: false); return; } Down(uid, dropHeldItems: false); _physics.SetLinearVelocity(uid, physics.LinearVelocity * 2f, body: physics); _statusEffects.TryAddStatusEffect(uid, FallStatusEffectKey, TimeSpan.FromSeconds(1), false); if (_random.Prob(_fallDeadChance) && _net.IsServer) { RaiseLocalEvent(uid, new PlayEmoteMessage(EmoteFallOnNeckProto)); } } public bool Stand(EntityUid uid, StandingStateComponent? standingState = null, AppearanceComponent? appearance = null, bool force = false) { if (!Resolve(uid, ref standingState, false)) return false; Resolve(uid, ref appearance, false); if (TryComp(uid, out var buckleComponent) && buckleComponent.Buckled) _buckle.TryUnbuckle(uid, uid, buckleComp: buckleComponent); if (IsStanding(uid, standingState)) return true; if (!force) { var msg = new StandAttemptEvent(); RaiseLocalEvent(uid, msg); if (msg.Cancelled) return false; } standingState.CurrentState = StandingState.Standing; Dirty(uid, standingState); RaiseLocalEvent(uid, new StoodEvent()); _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Vertical, appearance); if (TryComp(uid, out FixturesComponent? fixtureComponent)) { foreach (var key in standingState.ChangedFixtures) { if (fixtureComponent.Fixtures.TryGetValue(key, out var fixture)) _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask | StandingCollisionLayer, fixtureComponent); } } standingState.ChangedFixtures.Clear(); _movement.RefreshMovementSpeedModifiers(uid); return true; } public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true, bool force = false, StandingStateComponent? standingState = null, AppearanceComponent? appearance = null, HandsComponent? hands = null) { if (!Resolve(uid, ref standingState, false)) return false; Resolve(uid, ref appearance, ref hands, false); if (TryComp(uid, out var buckleComponent) && buckleComponent.Buckled) _buckle.TryUnbuckle(uid, uid, buckleComp: buckleComponent); if (IsDown(uid, standingState)) return true; if (dropHeldItems && hands != null) RaiseLocalEvent(uid, new DropHandItemsEvent()); if (!force) { var msg = new DownAttemptEvent(); RaiseLocalEvent(uid, msg); if (msg.Cancelled) return false; } standingState.CurrentState = StandingState.Laying; Dirty(uid, standingState); var transform = Transform(uid); var rotation = transform.LocalRotation; _appearance.TryGetData(uid, BuckleVisuals.Buckled, out var buckled, appearance); if (!buckled && (!_appearance.TryGetData(uid, MobStateVisuals.State, out var state, appearance) || state is MobState.Alive)) { if (rotation.GetDir() is Direction.East or Direction.North or Direction.NorthEast or Direction.SouthEast) _rotation.SetHorizontalAngle(uid, Angle.FromDegrees(270)); else _rotation.ResetHorizontalAngle(uid); } RaiseLocalEvent(uid, new DownedEvent()); _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Horizontal, appearance); if (TryComp(uid, out FixturesComponent? fixtureComponent)) { foreach (var (key, fixture) in fixtureComponent.Fixtures) { if ((fixture.CollisionMask & StandingCollisionLayer) == 0) continue; standingState.ChangedFixtures.Add(key); _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask & ~StandingCollisionLayer, manager: fixtureComponent); } } _movement.RefreshMovementSpeedModifiers(uid); if (_net.IsServer && playSound) _audio.PlayPvs(standingState.DownSound, uid); return true; } #endregion #region Helpers public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null) { if (!Resolve(uid, ref standingState, false)) return false; return standingState.CurrentState == StandingState.Laying; } public bool IsStanding(EntityUid uid, StandingStateComponent? standingState = null) { if (!Resolve(uid, ref standingState, false)) return false; return standingState.CurrentState == StandingState.Standing; } #endregion }