From d5f0c550a3bebe1426d76640f7275ebdbe23b84f Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 18 Sep 2024 22:18:35 -0400 Subject: [PATCH] Better Lying Down System (From White Dream) (#815) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Port of https://github.com/WWhiteDreamProject/wwdpublic/pull/2 And now also https://github.com/WWhiteDreamProject/wwdpublic/pull/8 Because Lying Down System is dependent on the Telescope System. # TODO - [x] Reconcile the code with core code, do code cleanup. I'll undraft this when I'm done. Probably not going to be tonight, because I will have to get some sleep soon to get up early for my calculus classes. # Changelog :cl: Spatison (White Dream) - add: Added lying down system / Добавлена система лежания - tweak: Lying down now uses do-afters that are visible to other people to indicate what is going on. - add: Added telescope system / Добавлена система прицеливания - tweak: Now you can aim from Hristov / Теперь можно прицеливаться из Христова --------- Signed-off-by: VMSolidus Co-authored-by: Spatison <137375981+Spatison@users.noreply.github.com> Co-authored-by: DEATHB4DEFEAT <77995199+DEATHB4DEFEAT@users.noreply.github.com> --- Content.Client/Buckle/BuckleSystem.cs | 12 +- Content.Client/Input/ContentContexts.cs | 1 + .../Options/UI/Tabs/KeyRebindTab.xaml.cs | 15 ++ Content.Client/Standing/LayingDownSystem.cs | 69 +++++ Content.Client/Telescope/TelescopeSystem.cs | 128 +++++++++ .../Standing/LayingDownComponent.cs | 14 - Content.Server/Standing/LayingDownSystem.cs | 95 +------ Content.Server/Telescope/TelescopeSystem.cs | 5 + .../Assorted/LayingDownModifierSystem.cs | 6 +- Content.Shared/CCVar/CCVars.cs | 10 + Content.Shared/Input/ContentKeyFunctions.cs | 1 + .../Standing/LayingDownComponent.cs | 26 ++ .../Standing/SharedLayingDownSystem.cs | 155 +++++++++++ .../Standing/StandingStateComponent.cs | 41 +-- .../Standing/StandingStateSystem.cs | 242 +++++++++--------- Content.Shared/Stunnable/SharedStunSystem.cs | 71 +++-- .../Telescope/SharedTelescopeSystem.cs | 110 ++++++++ .../Telescope/TelescopeComponent.cs | 16 ++ .../en-US/escape-menu/ui/options-menu.ftl | 5 + .../Locale/ru-RU/Escape-Menu/options-menu.ftl | 3 + .../Objects/Weapons/Guns/Snipers/snipers.yml | 1 + Resources/keybinds.yml | 3 + 22 files changed, 742 insertions(+), 287 deletions(-) create mode 100644 Content.Client/Standing/LayingDownSystem.cs create mode 100644 Content.Client/Telescope/TelescopeSystem.cs delete mode 100644 Content.Server/Standing/LayingDownComponent.cs create mode 100644 Content.Server/Telescope/TelescopeSystem.cs create mode 100644 Content.Shared/Standing/LayingDownComponent.cs create mode 100644 Content.Shared/Standing/SharedLayingDownSystem.cs create mode 100644 Content.Shared/Telescope/SharedTelescopeSystem.cs create mode 100644 Content.Shared/Telescope/TelescopeComponent.cs create mode 100644 Resources/Locale/ru-RU/Escape-Menu/options-menu.ftl diff --git a/Content.Client/Buckle/BuckleSystem.cs b/Content.Client/Buckle/BuckleSystem.cs index fea18e5cf3c..d4614210d9f 100644 --- a/Content.Client/Buckle/BuckleSystem.cs +++ b/Content.Client/Buckle/BuckleSystem.cs @@ -50,17 +50,11 @@ private void OnBuckleAfterAutoHandleState(EntityUid uid, BuckleComponent compone private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args) { - if (!TryComp(uid, out var rotVisuals)) + if (!TryComp(uid, out var rotVisuals) + || !Appearance.TryGetData(uid, BuckleVisuals.Buckled, out var buckled, args.Component) + || !buckled || args.Sprite == null) return; - if (!Appearance.TryGetData(uid, BuckleVisuals.Buckled, out var buckled, args.Component) || - !buckled || - args.Sprite == null) - { - _rotationVisualizerSystem.SetHorizontalAngle((uid, rotVisuals), rotVisuals.DefaultRotation); - return; - } - // Animate strapping yourself to something at a given angle // TODO: Dump this when buckle is better _rotationVisualizerSystem.AnimateSpriteRotation(uid, args.Sprite, rotVisuals.HorizontalRotation, 0.125f); diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 503a9ac953b..0e56153752d 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -82,6 +82,7 @@ public static void SetupContexts(IInputContextContainer contexts) human.AddFunction(ContentKeyFunctions.Arcade1); human.AddFunction(ContentKeyFunctions.Arcade2); human.AddFunction(ContentKeyFunctions.Arcade3); + human.AddFunction(ContentKeyFunctions.LookUp); // actions should be common (for ghosts, mobs, etc) common.AddFunction(ContentKeyFunctions.OpenActionsMenu); diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index 198eb5e4fb0..ab4ebd83fa7 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -97,6 +97,12 @@ private void HandleToggleWalk(BaseButton.ButtonToggledEventArgs args) _deferCommands.Add(_inputManager.SaveToUserData); } + private void HandleHoldLookUp(BaseButton.ButtonToggledEventArgs args) + { + _cfg.SetCVar(CCVars.HoldLookUp, args.Pressed); + _cfg.SaveToFile(); + } + private void HandleDefaultWalk(BaseButton.ButtonToggledEventArgs args) { _cfg.SetCVar(CCVars.DefaultWalk, args.Pressed); @@ -109,6 +115,12 @@ private void HandleStaticStorageUI(BaseButton.ButtonToggledEventArgs args) _cfg.SaveToFile(); } + private void HandleToggleAutoGetUp(BaseButton.ButtonToggledEventArgs args) + { + _cfg.SetCVar(CCVars.AutoGetUp, args.Pressed); + _cfg.SaveToFile(); + } + public KeyRebindTab() { IoCManager.InjectDependencies(this); @@ -193,6 +205,9 @@ void AddCheckBox(string checkBoxName, bool currentState, Action(OnMovementInput); + + SubscribeNetworkEvent(OnCheckAutoGetUp); + } + + private void OnMovementInput(EntityUid uid, LayingDownComponent component, MoveEvent args) + { + if (!_timing.IsFirstTimePredicted + || !_actionBlocker.CanMove(uid) + || _animation.HasRunningAnimation(uid, "rotate") + || !TryComp(uid, out var transform) + || !TryComp(uid, out var sprite) + || !TryComp(uid, out var rotationVisuals)) + return; + + var rotation = transform.LocalRotation + (_eyeManager.CurrentEye.Rotation - (transform.LocalRotation - transform.WorldRotation)); + + if (rotation.GetDir() is Direction.SouthEast or Direction.East or Direction.NorthEast or Direction.North) + { + rotationVisuals.HorizontalRotation = Angle.FromDegrees(270); + sprite.Rotation = Angle.FromDegrees(270); + return; + } + + rotationVisuals.HorizontalRotation = Angle.FromDegrees(90); + sprite.Rotation = Angle.FromDegrees(90); + } + + private void OnCheckAutoGetUp(CheckAutoGetUpEvent ev, EntitySessionEventArgs args) + { + if (!_timing.IsFirstTimePredicted) + return; + + var uid = GetEntity(ev.User); + + if (!TryComp(uid, out var transform) || !TryComp(uid, out var rotationVisuals)) + return; + + var rotation = transform.LocalRotation + (_eyeManager.CurrentEye.Rotation - (transform.LocalRotation - transform.WorldRotation)); + + if (rotation.GetDir() is Direction.SouthEast or Direction.East or Direction.NorthEast or Direction.North) + { + rotationVisuals.HorizontalRotation = Angle.FromDegrees(270); + return; + } + + rotationVisuals.HorizontalRotation = Angle.FromDegrees(90); + } +} diff --git a/Content.Client/Telescope/TelescopeSystem.cs b/Content.Client/Telescope/TelescopeSystem.cs new file mode 100644 index 00000000000..ac2270aa971 --- /dev/null +++ b/Content.Client/Telescope/TelescopeSystem.cs @@ -0,0 +1,128 @@ +using System.Numerics; +using Content.Client.Viewport; +using Content.Shared.CCVar; +using Content.Shared.Telescope; +using Content.Shared.Input; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Input; +using Robust.Client.Player; +using Robust.Client.UserInterface; +using Robust.Shared.Configuration; +using Robust.Shared.Input; +using Robust.Shared.Input.Binding; +using Robust.Shared.Timing; + +namespace Content.Client.Telescope; + +public sealed class TelescopeSystem : SharedTelescopeSystem +{ + [Dependency] private readonly InputSystem _inputSystem = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IInputManager _input = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IUserInterfaceManager _uiManager = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + + private ScalingViewport? _viewport; + private bool _holdLookUp; + private bool _toggled; + + public override void Initialize() + { + base.Initialize(); + + _cfg.OnValueChanged(CCVars.HoldLookUp, + val => + { + var input = val ? null : InputCmdHandler.FromDelegate(_ => _toggled = !_toggled); + _input.SetInputCommand(ContentKeyFunctions.LookUp, input); + _holdLookUp = val; + _toggled = false; + }, + true); + } + + public override void FrameUpdate(float frameTime) + { + base.FrameUpdate(frameTime); + + if (_timing.ApplyingState + || !_timing.IsFirstTimePredicted + || !_input.MouseScreenPosition.IsValid) + return; + + var player = _player.LocalEntity; + + var telescope = GetRightTelescope(player); + + if (telescope == null) + { + _toggled = false; + return; + } + + if (!TryComp(player, out var eye)) + return; + + var offset = Vector2.Zero; + + if (_holdLookUp) + { + if (_inputSystem.CmdStates.GetState(ContentKeyFunctions.LookUp) != BoundKeyState.Down) + { + RaiseEvent(offset); + return; + } + } + else if (!_toggled) + { + RaiseEvent(offset); + return; + } + + var mousePos = _input.MouseScreenPosition; + + if (_uiManager.MouseGetControl(mousePos) as ScalingViewport is { } viewport) + _viewport = viewport; + + if (_viewport == null) + return; + + var centerPos = _eyeManager.WorldToScreen(eye.Eye.Position.Position + eye.Offset); + + var diff = mousePos.Position - centerPos; + var len = diff.Length(); + + var size = _viewport.PixelSize; + + var maxLength = Math.Min(size.X, size.Y) * 0.4f; + var minLength = maxLength * 0.2f; + + if (len > maxLength) + { + diff *= maxLength / len; + len = maxLength; + } + + var divisor = maxLength * telescope.Divisor; + + if (len > minLength) + { + diff -= diff * minLength / len; + offset = new Vector2(diff.X / divisor, -diff.Y / divisor); + offset = new Angle(-eye.Rotation.Theta).RotateVec(offset); + } + + RaiseEvent(offset); + } + + private void RaiseEvent(Vector2 offset) + { + RaisePredictiveEvent(new EyeOffsetChangedEvent + { + Offset = offset + }); + } +} diff --git a/Content.Server/Standing/LayingDownComponent.cs b/Content.Server/Standing/LayingDownComponent.cs deleted file mode 100644 index 7921749f148..00000000000 --- a/Content.Server/Standing/LayingDownComponent.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Content.Server.Standing; - -[RegisterComponent] -public sealed partial class LayingDownComponent : Component -{ - [DataField] - public float DownedSpeedMultiplier = 0.15f; - - [DataField] - public TimeSpan Cooldown = TimeSpan.FromSeconds(2.5f); - - [DataField] - public TimeSpan NextToggleAttempt = TimeSpan.Zero; -} diff --git a/Content.Server/Standing/LayingDownSystem.cs b/Content.Server/Standing/LayingDownSystem.cs index 73a929fdfc4..e5054bdd706 100644 --- a/Content.Server/Standing/LayingDownSystem.cs +++ b/Content.Server/Standing/LayingDownSystem.cs @@ -1,101 +1,28 @@ -using Content.Shared.ActionBlocker; -using Content.Shared.Input; -using Content.Shared.Movement.Systems; -using Content.Shared.Popups; using Content.Shared.Standing; -using Robust.Shared.Input.Binding; -using Robust.Shared.Player; -using Robust.Shared.Timing; +using Content.Shared.CCVar; +using Robust.Shared.Configuration; namespace Content.Server.Standing; -/// Unfortunately cannot be shared because some standing conditions are server-side only -public sealed class LayingDownSystem : EntitySystem +public sealed class LayingDownSystem : SharedLayingDownSystem { - [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; - [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; - [Dependency] private readonly SharedPopupSystem _popups = default!; - [Dependency] private readonly Shared.Standing.StandingStateSystem _standing = default!; // WHY IS THERE TWO DIFFERENT STANDING SYSTEMS?! - [Dependency] private readonly IGameTiming _timing = default!; - + [Dependency] private readonly INetConfigurationManager _cfg = default!; public override void Initialize() { - CommandBinds.Builder - .Bind(ContentKeyFunctions.ToggleStanding, InputCmdHandler.FromDelegate(ToggleStanding, handle: false, outsidePrediction: false)) - .Register(); - - SubscribeLocalEvent(DoRefreshMovementSpeed); - SubscribeLocalEvent(DoRefreshMovementSpeed); - SubscribeLocalEvent(OnRefreshMovementSpeed); - SubscribeLocalEvent(OnParentChanged); - } - - public override void Shutdown() - { - base.Shutdown(); - - CommandBinds.Unregister(); - } + base.Initialize(); - private void DoRefreshMovementSpeed(EntityUid uid, LayingDownComponent component, object args) - { - _movement.RefreshMovementSpeedModifiers(uid); + SubscribeNetworkEvent(OnCheckAutoGetUp); } - private void OnRefreshMovementSpeed(EntityUid uid, LayingDownComponent component, RefreshMovementSpeedModifiersEvent args) + private void OnCheckAutoGetUp(CheckAutoGetUpEvent ev, EntitySessionEventArgs args) { - if (TryComp(uid, out var standingState) && standingState.Standing) - return; + var uid = GetEntity(ev.User); - args.ModifySpeed(component.DownedSpeedMultiplier, component.DownedSpeedMultiplier, bypassImmunity: true); - } - - private void OnParentChanged(EntityUid uid, LayingDownComponent component, EntParentChangedMessage args) - { - // If the entity is not on a grid, try to make it stand up to avoid issues - if (!TryComp(uid, out var standingState) - || standingState.Standing - || Transform(uid).GridUid != null) + if (!TryComp(uid, out LayingDownComponent? layingDown)) return; - _standing.Stand(uid, standingState); - } - - private void ToggleStanding(ICommonSession? session) - { - if (session is not { AttachedEntity: { Valid: true } uid } playerSession - || !Exists(uid) - || !TryComp(uid, out var standingState) - || !TryComp(uid, out var layingDown)) - return; - - // If successful, show popup to self and others. Otherwise, only to self. - if (ToggleStandingImpl(uid, standingState, layingDown, out var popupBranch)) - { - _popups.PopupEntity(Loc.GetString($"laying-comp-{popupBranch}-other", ("entity", uid)), uid, Filter.PvsExcept(uid), true); - layingDown.NextToggleAttempt = _timing.CurTime + layingDown.Cooldown; - } - - _popups.PopupEntity(Loc.GetString($"laying-comp-{popupBranch}-self", ("entity", uid)), uid, uid); - } - - private bool ToggleStandingImpl(EntityUid uid, StandingStateComponent standingState, LayingDownComponent layingDown, out string popupBranch) - { - var success = layingDown.NextToggleAttempt <= _timing.CurTime; - - if (_standing.IsDown(uid, standingState)) - { - success = success && _standing.Stand(uid, standingState, force: false); - popupBranch = success ? "stand-success" : "stand-fail"; - } - else - { - success = success && Transform(uid).GridUid != null; // Do not allow laying down when not on a surface. - success = success && _standing.Down(uid, standingState: standingState, playSound: true, dropHeldItems: false); - popupBranch = success ? "lay-success" : "lay-fail"; - } - - return success; + layingDown.AutoGetUp = _cfg.GetClientCVar(args.SenderSession.Channel, CCVars.AutoGetUp); + Dirty(uid, layingDown); } } diff --git a/Content.Server/Telescope/TelescopeSystem.cs b/Content.Server/Telescope/TelescopeSystem.cs new file mode 100644 index 00000000000..0e53cc15a20 --- /dev/null +++ b/Content.Server/Telescope/TelescopeSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Telescope; + +namespace Content.Server.Telescope; + +public sealed class TelescopeSystem : SharedTelescopeSystem; diff --git a/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs b/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs index dc6dcd2de3b..e4a63d6108b 100644 --- a/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs +++ b/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs @@ -1,5 +1,5 @@ using Content.Server.Traits.Assorted; -using Content.Server.Standing; +using Content.Shared.Standing; namespace Content.Shared.Traits.Assorted.Systems; @@ -16,7 +16,7 @@ private void OnStartup(EntityUid uid, LayingDownModifierComponent component, Com if (!TryComp(uid, out var layingDown)) return; - layingDown.Cooldown *= component.LayingDownCooldownMultiplier; - layingDown.DownedSpeedMultiplier *= component.DownedSpeedMultiplierMultiplier; + layingDown.StandingUpTime *= component.LayingDownCooldownMultiplier; + layingDown.SpeedModify *= component.DownedSpeedMultiplierMultiplier; } } diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index bad706f8154..27ad9641070 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -2457,6 +2457,16 @@ public static readonly CVarDef #endregion + #region Lying Down System + + public static readonly CVarDef AutoGetUp = + CVarDef.Create("rest.auto_get_up", true, CVar.CLIENT | CVar.ARCHIVE | CVar.REPLICATED); + + public static readonly CVarDef HoldLookUp = + CVarDef.Create("rest.hold_look_up", false, CVar.CLIENT | CVar.ARCHIVE); + + #endregion + #region Material Reclaimer /// diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index dac780783c7..f85983282c7 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -57,6 +57,7 @@ public static class ContentKeyFunctions public static readonly BoundKeyFunction ResetZoom = "ResetZoom"; public static readonly BoundKeyFunction OfferItem = "OfferItem"; public static readonly BoundKeyFunction ToggleStanding = "ToggleStanding"; + public static readonly BoundKeyFunction LookUp = "LookUp"; public static readonly BoundKeyFunction ArcadeUp = "ArcadeUp"; public static readonly BoundKeyFunction ArcadeDown = "ArcadeDown"; diff --git a/Content.Shared/Standing/LayingDownComponent.cs b/Content.Shared/Standing/LayingDownComponent.cs new file mode 100644 index 00000000000..1499704c53b --- /dev/null +++ b/Content.Shared/Standing/LayingDownComponent.cs @@ -0,0 +1,26 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Standing; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class LayingDownComponent : Component +{ + [DataField, AutoNetworkedField] + public float StandingUpTime { get; set; } = 1f; + + [DataField, AutoNetworkedField] + public float SpeedModify { get; set; } = 0.4f; + + [DataField, AutoNetworkedField] + public bool AutoGetUp; +} + +[Serializable, NetSerializable] +public sealed class ChangeLayingDownEvent : CancellableEntityEventArgs; + +[Serializable, NetSerializable] +public sealed class CheckAutoGetUpEvent(NetEntity user) : CancellableEntityEventArgs +{ + public NetEntity User = user; +} diff --git a/Content.Shared/Standing/SharedLayingDownSystem.cs b/Content.Shared/Standing/SharedLayingDownSystem.cs new file mode 100644 index 00000000000..47ad01949cb --- /dev/null +++ b/Content.Shared/Standing/SharedLayingDownSystem.cs @@ -0,0 +1,155 @@ +using Content.Shared.DoAfter; +using Content.Shared.Gravity; +using Content.Shared.Input; +using Content.Shared.Mobs.Systems; +using Content.Shared.Movement.Systems; +using Content.Shared.Standing; +using Content.Shared.Stunnable; +using Robust.Shared.Input.Binding; +using Robust.Shared.Player; +using Robust.Shared.Serialization; + +namespace Content.Shared.Standing; + +public abstract class SharedLayingDownSystem : EntitySystem +{ + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly StandingStateSystem _standing = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; + + public override void Initialize() + { + CommandBinds.Builder + .Bind(ContentKeyFunctions.ToggleStanding, InputCmdHandler.FromDelegate(ToggleStanding)) + .Register(); + + SubscribeNetworkEvent(OnChangeState); + + SubscribeLocalEvent(OnStandingUpDoAfter); + SubscribeLocalEvent(OnRefreshMovementSpeed); + SubscribeLocalEvent(OnParentChanged); + } + + public override void Shutdown() + { + base.Shutdown(); + + CommandBinds.Unregister(); + } + + private void ToggleStanding(ICommonSession? session) + { + if (session?.AttachedEntity == null + || !HasComp(session.AttachedEntity) + || _gravity.IsWeightless(session.AttachedEntity.Value)) + return; + + RaiseNetworkEvent(new ChangeLayingDownEvent()); + } + + private void OnChangeState(ChangeLayingDownEvent ev, EntitySessionEventArgs args) + { + if (!args.SenderSession.AttachedEntity.HasValue) + return; + + var uid = args.SenderSession.AttachedEntity.Value; + + // TODO: Wizard + //if (HasComp(uid)) + // return; + + if (!TryComp(uid, out StandingStateComponent? standing) + || !TryComp(uid, out LayingDownComponent? layingDown)) + return; + + RaiseNetworkEvent(new CheckAutoGetUpEvent(GetNetEntity(uid))); + + if (HasComp(uid) + || !_mobState.IsAlive(uid)) + return; + + if (_standing.IsDown(uid, standing)) + TryStandUp(uid, layingDown, standing); + else + TryLieDown(uid, layingDown, standing); + } + + private void OnStandingUpDoAfter(EntityUid uid, StandingStateComponent component, StandingUpDoAfterEvent args) + { + if (args.Handled || args.Cancelled + || HasComp(uid) + || _mobState.IsIncapacitated(uid) + || !_standing.Stand(uid)) + component.CurrentState = StandingState.Lying; + + component.CurrentState = StandingState.Standing; + } + + private void OnRefreshMovementSpeed(EntityUid uid, LayingDownComponent component, RefreshMovementSpeedModifiersEvent args) + { + if (_standing.IsDown(uid)) + args.ModifySpeed(component.SpeedModify, component.SpeedModify); + else + args.ModifySpeed(1f, 1f); + } + + private void OnParentChanged(EntityUid uid, LayingDownComponent component, EntParentChangedMessage args) + { + // If the entity is not on a grid, try to make it stand up to avoid issues + if (!TryComp(uid, out var standingState) + || standingState.CurrentState is StandingState.Standing + || Transform(uid).GridUid != null) + return; + + _standing.Stand(uid, standingState); + } + + public bool TryStandUp(EntityUid uid, LayingDownComponent? layingDown = null, StandingStateComponent? standingState = null) + { + if (!Resolve(uid, ref standingState, false) + || !Resolve(uid, ref layingDown, false) + || standingState.CurrentState is not StandingState.Lying + || !_mobState.IsAlive(uid) + || TerminatingOrDeleted(uid)) + return false; + + var args = new DoAfterArgs(EntityManager, uid, layingDown.StandingUpTime, new StandingUpDoAfterEvent(), uid) + { + BreakOnHandChange = false, + RequireCanInteract = false + }; + + if (!_doAfter.TryStartDoAfter(args)) + return false; + + standingState.CurrentState = StandingState.GettingUp; + return true; + } + + public bool TryLieDown(EntityUid uid, LayingDownComponent? layingDown = null, StandingStateComponent? standingState = null, DropHeldItemsBehavior behavior = DropHeldItemsBehavior.NoDrop) + { + if (!Resolve(uid, ref standingState, false) + || !Resolve(uid, ref layingDown, false) + || standingState.CurrentState is not StandingState.Standing) + { + if (behavior == DropHeldItemsBehavior.AlwaysDrop) + RaiseLocalEvent(uid, new DropHandItemsEvent()); + + return false; + } + + _standing.Down(uid, true, behavior != DropHeldItemsBehavior.NoDrop, standingState); + return true; + } +} + +[Serializable, NetSerializable] +public sealed partial class StandingUpDoAfterEvent : SimpleDoAfterEvent; + +public enum DropHeldItemsBehavior : byte +{ + NoDrop, + DropIfStanding, + AlwaysDrop +} diff --git a/Content.Shared/Standing/StandingStateComponent.cs b/Content.Shared/Standing/StandingStateComponent.cs index 5d7bb0a59fd..5b9759a0252 100644 --- a/Content.Shared/Standing/StandingStateComponent.cs +++ b/Content.Shared/Standing/StandingStateComponent.cs @@ -1,24 +1,31 @@ using Robust.Shared.Audio; using Robust.Shared.GameStates; -namespace Content.Shared.Standing +namespace Content.Shared.Standing; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class StandingStateComponent : Component { - [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] - [Access(typeof(StandingStateSystem))] - public sealed partial class StandingStateComponent : Component - { - [ViewVariables(VVAccess.ReadWrite)] - [DataField] - public SoundSpecifier DownSound { get; private set; } = new SoundCollectionSpecifier("BodyFall"); + [DataField] + public SoundSpecifier DownSound { get; private set; } = new SoundCollectionSpecifier("BodyFall"); + + [DataField, AutoNetworkedField] + public StandingState CurrentState { get; set; } = StandingState.Standing; - [DataField, AutoNetworkedField] - public bool Standing { get; set; } = true; + [DataField, AutoNetworkedField] + public bool Standing { get; set; } = true; - /// - /// List of fixtures that had their collision mask changed when the entity was downed. - /// Required for re-adding the collision mask. - /// - [DataField, AutoNetworkedField] - public List ChangedFixtures = new(); - } + /// + /// List of fixtures that had their collision mask changed when the entity was downed. + /// Required for re-adding the collision mask. + /// + [DataField, AutoNetworkedField] + public List ChangedFixtures = new(); +} + +public enum StandingState +{ + Lying, + GettingUp, + Standing, } diff --git a/Content.Shared/Standing/StandingStateSystem.cs b/Content.Shared/Standing/StandingStateSystem.cs index 517831b8a1b..69e72033bba 100644 --- a/Content.Shared/Standing/StandingStateSystem.cs +++ b/Content.Shared/Standing/StandingStateSystem.cs @@ -1,166 +1,160 @@ +using Content.Shared.Buckle; +using Content.Shared.Buckle.Components; using Content.Shared.Hands.Components; +using Content.Shared.Movement.Systems; using Content.Shared.Physics; using Content.Shared.Rotation; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Physics; using Robust.Shared.Physics.Systems; -namespace Content.Shared.Standing +namespace Content.Shared.Standing; + +public sealed class StandingStateSystem : EntitySystem { - public sealed class StandingStateSystem : EntitySystem - { - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; + [Dependency] private readonly SharedBuckleSystem _buckle = default!; - // If StandingCollisionLayer value is ever changed to more than one layer, the logic needs to be edited. - private const int StandingCollisionLayer = (int) CollisionGroup.MidImpassable; + // If StandingCollisionLayer value is ever changed to more than one layer, the logic needs to be edited. + private const int StandingCollisionLayer = (int) CollisionGroup.MidImpassable; - public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null) - { - if (!Resolve(uid, ref standingState, false)) - return false; - - return !standingState.Standing; - } + public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null) + { + if (!Resolve(uid, ref standingState, false)) + return false; - public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true, - StandingStateComponent? standingState = null, - AppearanceComponent? appearance = null, - HandsComponent? hands = null) - { - // TODO: This should actually log missing comps... - if (!Resolve(uid, ref standingState, false)) - return false; + return standingState.CurrentState is StandingState.Lying or StandingState.GettingUp; + } - // Optional component. - Resolve(uid, ref appearance, ref hands, false); + public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true, + StandingStateComponent? standingState = null, + AppearanceComponent? appearance = null, + HandsComponent? hands = null) + { + // TODO: This should actually log missing comps... + if (!Resolve(uid, ref standingState, false)) + return false; - if (!standingState.Standing) - return true; + // Optional component. + Resolve(uid, ref appearance, ref hands, false); - // This is just to avoid most callers doing this manually saving boilerplate - // 99% of the time you'll want to drop items but in some scenarios (e.g. buckling) you don't want to. - // We do this BEFORE downing because something like buckle may be blocking downing but we want to drop hand items anyway - // and ultimately this is just to avoid boilerplate in Down callers + keep their behavior consistent. - if (dropHeldItems && hands != null) - { - RaiseLocalEvent(uid, new DropHandItemsEvent(), false); - } + if (standingState.CurrentState is StandingState.Lying or StandingState.GettingUp) + return true; - var msg = new DownAttemptEvent(); - RaiseLocalEvent(uid, msg, false); + // This is just to avoid most callers doing this manually saving boilerplate + // 99% of the time you'll want to drop items but in some scenarios (e.g. buckling) you don't want to. + // We do this BEFORE downing because something like buckle may be blocking downing but we want to drop hand items anyway + // and ultimately this is just to avoid boilerplate in Down callers + keep their behavior consistent. + if (dropHeldItems && hands != null) + RaiseLocalEvent(uid, new DropHandItemsEvent(), false); - if (msg.Cancelled) - return false; + if (TryComp(uid, out BuckleComponent? buckle) && buckle.Buckled && !_buckle.TryUnbuckle(uid, uid, buckleComp: buckle)) + return false; - standingState.Standing = false; - Dirty(standingState); - RaiseLocalEvent(uid, new DownedEvent(), false); + var msg = new DownAttemptEvent(); + RaiseLocalEvent(uid, msg, false); - // Seemed like the best place to put it - _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Horizontal, appearance); + if (msg.Cancelled) + return false; - // Change collision masks to allow going under certain entities like flaps and tables - 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); - } - } + standingState.CurrentState = StandingState.Lying; + Dirty(standingState); + RaiseLocalEvent(uid, new DownedEvent(), false); - // check if component was just added or streamed to client - // if true, no need to play sound - mob was down before player could seen that - if (standingState.LifeStage <= ComponentLifeStage.Starting) - return true; + // Seemed like the best place to put it + _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Horizontal, appearance); - if (playSound) + // Change collision masks to allow going under certain entities like flaps and tables + if (TryComp(uid, out FixturesComponent? fixtureComponent)) + foreach (var (key, fixture) in fixtureComponent.Fixtures) { - _audio.PlayPredicted(standingState.DownSound, uid, uid); + if ((fixture.CollisionMask & StandingCollisionLayer) == 0) + continue; + + standingState.ChangedFixtures.Add(key); + _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask & ~StandingCollisionLayer, manager: fixtureComponent); } + // check if component was just added or streamed to client + // if true, no need to play sound - mob was down before player could seen that + if (standingState.LifeStage <= ComponentLifeStage.Starting) return true; - } - public bool Stand(EntityUid uid, - StandingStateComponent? standingState = null, - AppearanceComponent? appearance = null, - bool force = false) - { - // TODO: This should actually log missing comps... - if (!Resolve(uid, ref standingState, false)) - return false; + if (playSound) + _audio.PlayPredicted(standingState.DownSound, uid, null); - // Optional component. - Resolve(uid, ref appearance, false); + _movement.RefreshMovementSpeedModifiers(uid); + return true; + } - if (standingState.Standing) - return true; + public bool Stand(EntityUid uid, + StandingStateComponent? standingState = null, + AppearanceComponent? appearance = null, + bool force = false) + { + // TODO: This should actually log missing comps... + if (!Resolve(uid, ref standingState, false)) + return false; - if (!force) - { - var msg = new StandAttemptEvent(); - RaiseLocalEvent(uid, msg, false); + // Optional component. + Resolve(uid, ref appearance, false); - if (msg.Cancelled) - return false; - } + if (standingState.CurrentState is StandingState.Standing + || TryComp(uid, out BuckleComponent? buckle) + && buckle.Buckled && !_buckle.TryUnbuckle(uid, uid, buckleComp: buckle)) + return true; - standingState.Standing = true; - Dirty(uid, standingState); - RaiseLocalEvent(uid, new StoodEvent(), false); + if (!force) + { + var msg = new StandAttemptEvent(); + RaiseLocalEvent(uid, msg, false); - _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Vertical, appearance); + if (msg.Cancelled) + return false; + } - if (TryComp(uid, out FixturesComponent? fixtureComponent)) + standingState.CurrentState = StandingState.Standing; + Dirty(uid, standingState); + RaiseLocalEvent(uid, new StoodEvent(), false); + + _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Vertical, appearance); + + if (TryComp(uid, out FixturesComponent? fixtureComponent)) + { + foreach (var key in standingState.ChangedFixtures) { - foreach (var key in standingState.ChangedFixtures) - { - if (fixtureComponent.Fixtures.TryGetValue(key, out var fixture)) - _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask | StandingCollisionLayer, fixtureComponent); - } + if (fixtureComponent.Fixtures.TryGetValue(key, out var fixture)) + _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask | StandingCollisionLayer, fixtureComponent); } - standingState.ChangedFixtures.Clear(); - - return true; } - } + standingState.ChangedFixtures.Clear(); + _movement.RefreshMovementSpeedModifiers(uid); - public sealed class DropHandItemsEvent : EventArgs - { + return true; } +} - /// - /// Subscribe if you can potentially block a down attempt. - /// - public sealed class DownAttemptEvent : CancellableEntityEventArgs - { - } +public sealed class DropHandItemsEvent : EventArgs { } - /// - /// Subscribe if you can potentially block a stand attempt. - /// - public sealed class StandAttemptEvent : CancellableEntityEventArgs - { - } +/// +/// Subscribe if you can potentially block a down attempt. +/// +public sealed class DownAttemptEvent : CancellableEntityEventArgs { } - /// - /// Raised when an entity becomes standing - /// - public sealed class StoodEvent : EntityEventArgs - { - } +/// +/// Subscribe if you can potentially block a stand attempt. +/// +public sealed class StandAttemptEvent : CancellableEntityEventArgs { } - /// - /// Raised when an entity is not standing - /// - public sealed class DownedEvent : EntityEventArgs - { - } -} +/// +/// Raised when an entity becomes standing +/// +public sealed class StoodEvent : EntityEventArgs { } + +/// +/// Raised when an entity is not standing +/// +public sealed class DownedEvent : EntityEventArgs { } diff --git a/Content.Shared/Stunnable/SharedStunSystem.cs b/Content.Shared/Stunnable/SharedStunSystem.cs index 976b0ab500d..52225843f23 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.cs @@ -19,6 +19,7 @@ using Content.Shared.Throwing; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Player; @@ -32,6 +33,8 @@ public abstract class SharedStunSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = 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!; /// /// Friction modifier for knocked down players. @@ -76,15 +79,12 @@ public override void Initialize() private void OnMobStateChanged(EntityUid uid, MobStateComponent component, MobStateChangedEvent args) { if (!TryComp(uid, out var status)) - { return; - } + switch (args.NewMobState) { case MobState.Alive: - { break; - } case MobState.Critical: { _statusEffect.TryRemoveStatusEffect(uid, "Stun"); @@ -109,12 +109,23 @@ private void UpdateCanMove(EntityUid uid, StunnedComponent component, EntityEven private void OnKnockInit(EntityUid uid, KnockedDownComponent component, ComponentInit args) { - _standingState.Down(uid); + RaiseNetworkEvent(new CheckAutoGetUpEvent(GetNetEntity(uid))); + _layingDown.TryLieDown(uid, null, null, DropHeldItemsBehavior.DropIfStanding); } private void OnKnockShutdown(EntityUid uid, KnockedDownComponent component, ComponentShutdown args) { - _standingState.Stand(uid); + 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) @@ -148,13 +159,9 @@ private void OnRefreshMovespeed(EntityUid uid, SlowedDownComponent component, Re public bool TryStun(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null) { - if (time <= TimeSpan.Zero) - return false; - - if (!Resolve(uid, ref status, false)) - return false; - - if (!_statusEffect.TryAddStatusEffect(uid, "Stun", time, refresh)) + if (time <= TimeSpan.Zero + || !Resolve(uid, ref status, false) + || !_statusEffect.TryAddStatusEffect(uid, "Stun", time, refresh)) return false; var ev = new StunnedEvent(); @@ -170,13 +177,9 @@ public bool TryStun(EntityUid uid, TimeSpan time, bool refresh, public bool TryKnockdown(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null) { - if (time <= TimeSpan.Zero) - return false; - - if (!Resolve(uid, ref status, false)) - return false; - - if (!_statusEffect.TryAddStatusEffect(uid, "KnockedDown", time, refresh)) + if (time <= TimeSpan.Zero + || !Resolve(uid, ref status, false) + || !_statusEffect.TryAddStatusEffect(uid, "KnockedDown", time, refresh)) return false; var ev = new KnockedDownEvent(); @@ -204,10 +207,8 @@ public bool TrySlowdown(EntityUid uid, TimeSpan time, bool refresh, float walkSpeedMultiplier = 1f, float runSpeedMultiplier = 1f, StatusEffectsComponent? status = null) { - if (!Resolve(uid, ref status, false)) - return false; - - if (time <= TimeSpan.Zero) + if (!Resolve(uid, ref status, false) + || time <= TimeSpan.Zero) return false; if (_statusEffect.TryAddStatusEffect(uid, "SlowedDown", time, refresh, status)) @@ -230,14 +231,8 @@ public bool TrySlowdown(EntityUid uid, TimeSpan time, bool refresh, private void OnInteractHand(EntityUid uid, KnockedDownComponent knocked, InteractHandEvent args) { - // This is currently disabled in favor of an interaction verb with the same effect, but more obvious usage. - return; - - if (args.Handled || knocked.HelpTimer > 0f) - return; - - // TODO: This should be an event. - if (HasComp(uid)) + if (args.Handled || knocked.HelpTimer > 0f + || HasComp(uid)) return; // Set it to half the help interval so helping is actually useful... @@ -273,15 +268,19 @@ private void OnAttempt(EntityUid uid, StunnedComponent stunned, CancellableEntit private void OnEquipAttempt(EntityUid uid, StunnedComponent stunned, IsEquippingAttemptEvent args) { // is this a self-equip, or are they being stripped? - if (args.Equipee == uid) - args.Cancel(); + 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) - args.Cancel(); + if (args.Unequipee != uid) + return; + + args.Cancel(); } #endregion diff --git a/Content.Shared/Telescope/SharedTelescopeSystem.cs b/Content.Shared/Telescope/SharedTelescopeSystem.cs new file mode 100644 index 00000000000..5f9896cc359 --- /dev/null +++ b/Content.Shared/Telescope/SharedTelescopeSystem.cs @@ -0,0 +1,110 @@ +using System.Numerics; +using Content.Shared.Camera; +using Content.Shared.Hands; +using Content.Shared.Hands.Components; +using Content.Shared.Item; +using Robust.Shared.Serialization; + +namespace Content.Shared.Telescope; + +public abstract class SharedTelescopeSystem : EntitySystem +{ + [Dependency] private readonly SharedEyeSystem _eye = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeAllEvent(OnEyeOffsetChanged); + SubscribeLocalEvent(OnUnequip); + SubscribeLocalEvent(OnHandDeselected); + SubscribeLocalEvent(OnShutdown); + } + + private void OnShutdown(Entity ent, ref ComponentShutdown args) + { + if (!TryComp(ent.Comp.LastEntity, out EyeComponent? eye) + || ent.Comp.LastEntity == ent && TerminatingOrDeleted(ent)) + return; + + SetOffset((ent.Comp.LastEntity.Value, eye), Vector2.Zero, ent); + } + + private void OnHandDeselected(Entity ent, ref HandDeselectedEvent args) + { + if (!TryComp(args.User, out EyeComponent? eye)) + return; + + SetOffset((args.User, eye), Vector2.Zero, ent); + } + + private void OnUnequip(Entity ent, ref GotUnequippedHandEvent args) + { + if (!TryComp(args.User, out EyeComponent? eye) + || !HasComp(ent.Owner)) + return; + + SetOffset((args.User, eye), Vector2.Zero, ent); + } + + public TelescopeComponent? GetRightTelescope(EntityUid? ent) + { + TelescopeComponent? telescope = null; + + if (TryComp(ent, out var hands) + && hands.ActiveHandEntity.HasValue + && TryComp(hands.ActiveHandEntity, out var handTelescope)) + telescope = handTelescope; + else if (TryComp(ent, out var entityTelescope)) + telescope = entityTelescope; + + return telescope; + } + + private void OnEyeOffsetChanged(EyeOffsetChangedEvent msg, EntitySessionEventArgs args) + { + if (args.SenderSession.AttachedEntity is not { } ent + || !TryComp(ent, out var eye)) + return; + + var telescope = GetRightTelescope(ent); + + if (telescope == null) + return; + + var offset = Vector2.Lerp(eye.Offset, msg.Offset, telescope.LerpAmount); + + SetOffset((ent, eye), offset, telescope); + } + + private void SetOffset(Entity ent, Vector2 offset, TelescopeComponent telescope) + { + telescope.LastEntity = ent; + + if (TryComp(ent, out CameraRecoilComponent? recoil)) + { + recoil.BaseOffset = offset; + _eye.SetOffset(ent, offset + recoil.CurrentKick, ent); + } + else + { + _eye.SetOffset(ent, offset, ent); + } + } + + public void SetParameters(Entity ent, float? divisor = null, float? lerpAmount = null) + { + var telescope = ent.Comp; + + telescope.Divisor = divisor ?? telescope.Divisor; + telescope.LerpAmount = lerpAmount ?? telescope.LerpAmount; + + Dirty(ent.Owner, telescope); + } +} + +[Serializable, NetSerializable] +public sealed class EyeOffsetChangedEvent : EntityEventArgs +{ + public Vector2 Offset; +} diff --git a/Content.Shared/Telescope/TelescopeComponent.cs b/Content.Shared/Telescope/TelescopeComponent.cs new file mode 100644 index 00000000000..529cc58324f --- /dev/null +++ b/Content.Shared/Telescope/TelescopeComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Telescope; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TelescopeComponent : Component +{ + [DataField, AutoNetworkedField] + public float Divisor = 0.1f; + + [DataField, AutoNetworkedField] + public float LerpAmount = 0.1f; + + [ViewVariables] + public EntityUid? LastEntity; +} diff --git a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl index 7d6af11f379..eaab10df027 100644 --- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl +++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl @@ -269,3 +269,8 @@ ui-options-net-pvs-leave-tooltip = This limits the rate at which the client will ## Toggle window console command cmd-options-desc = Opens options menu, optionally with a specific tab selected. cmd-options-help = Usage: options [tab] + +## Combat Options +ui-options-function-look-up = Look up/Take aim +ui-options-function-auto-get-up = Automatically get up after falling +ui-options-function-hold-look-up = Hold down the key to aim diff --git a/Resources/Locale/ru-RU/Escape-Menu/options-menu.ftl b/Resources/Locale/ru-RU/Escape-Menu/options-menu.ftl new file mode 100644 index 00000000000..a39bc37af90 --- /dev/null +++ b/Resources/Locale/ru-RU/Escape-Menu/options-menu.ftl @@ -0,0 +1,3 @@ +ui-options-function-look-up = Присмотреться/Прицелиться +ui-options-function-auto-get-up = Автоматически вставать при падении +ui-options-function-hold-look-up = Удерживать клавишу для прицеливания diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml index 4b58642c307..c85314e91ca 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml @@ -81,6 +81,7 @@ - CartridgeAntiMateriel capacity: 5 proto: CartridgeAntiMateriel + - type: Telescope - type: entity name: musket diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 2cca749317a..33b4166161a 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -543,3 +543,6 @@ binds: - function: Hotbar9 type: State key: Num9 +- function: LookUp + type: State + key: Space