From 5192d120eae1457528e8056e5f6208f70fa85395 Mon Sep 17 00:00:00 2001 From: haiwwkes <49613070+rhailrake@users.noreply.github.com> Date: Wed, 25 Dec 2024 01:27:09 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=BE=D0=B2=D0=B0=D1=8F=20=D0=BF=D0=B0?= =?UTF-8?q?=D0=BD=D0=B5=D0=BB=D1=8C=20=D1=8D=D0=BC=D0=BE=D1=83=D1=82=D0=BE?= =?UTF-8?q?=D0=B2.=20(#891)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wacky * work * work * work * work * eff * a --- Content.Client/Buckle/BuckleSystem.cs | 1 - Content.Client/Chat/UI/EmotesMenu.xaml.cs | 2 +- Content.Client/Input/ContentContexts.cs | 4 + .../Options/UI/Tabs/KeyRebindTab.xaml.cs | 4 + .../Standing/StandingStateSystem.cs | 67 ++++ .../Screens/SeparatedChatGameScreen.xaml | 2 + .../Systems/Emotes/EmotesUIController.cs | 2 +- .../MenuBar/Widgets/GameTopMenuBar.xaml | 2 + .../Animations/EmoteAnimationSystem.cs | 132 ++++++++ .../UserActions/Tabs/BaseTabControl.cs | 9 + .../UserActions/Tabs/EmotesTabControl.cs | 183 ++++++++++ .../UserActions/Tabs/EmotesTabControl.xaml | 20 ++ .../UserActions/Tabs/VerbsTabControl.cs | 184 ++++++++++ .../UserActions/Tabs/VerbsTabControl.xaml | 20 ++ .../UserActions/UserActionUIController.cs | 45 +++ .../UserActions/UserActionUISystem.cs | 23 ++ .../_Sunrise/UserActions/UserActionsPanel.cs | 49 +++ .../UserActions/UserActionsPanel.xaml | 27 ++ .../Tests/Buckle/BuckleTest.cs | 2 +- Content.Server/Morgue/CrematoriumSystem.cs | 2 +- .../NPC/Systems/NPCCombatSystem.Ranged.cs | 1 + Content.Server/Speech/EmotesMenuSystem.cs | 2 +- .../Standing/StandingStateSystem.cs | 69 ++-- .../Animations/EmoteAnimationSystem.cs | 52 +++ .../_Sunrise/Carrying/CarryingSystem.cs | 2 +- .../_Sunrise/Footprints/FootPrintsSystem.cs | 2 +- .../Footprints/PuddleFootPrintsSystem.cs | 4 +- .../Body/Systems/SharedBodySystem.cs | 2 +- Content.Shared/Buckle/SharedBuckleSystem.cs | 2 +- .../Chat/Prototypes/EmotePrototype.cs | 1 + Content.Shared/Input/ContentKeyFunctions.cs | 4 + .../Medical/Cryogenics/SharedCryoPodSystem.cs | 2 +- .../Systems/MobStateSystem.Subscribers.cs | 4 +- Content.Shared/Mobs/Systems/MobStateSystem.cs | 2 +- .../EntityStorageLayingDownOverrideSystem.cs | 2 +- .../Standing/SharedStandingStateSystem.cs | 316 ++++++++++++++++++ .../Standing/StandingStateComponent.cs | 41 ++- .../Standing/StandingStateEvents.cs | 28 ++ .../Standing/StandingStateSystem.cs | 172 ---------- Content.Shared/Stunnable/SharedStunSystem.cs | 2 +- .../Traits/Assorted/LegsParalyzedSystem.cs | 2 +- .../Weapons/Ranged/Systems/SharedGunSystem.cs | 5 + .../Animations/EmoteAnimationComponent.cs | 29 ++ .../Medical/Surgery/SharedSurgerySystem.cs | 2 +- .../user-action-control.ftl | 3 + .../user-action-control.ftl | 3 + .../Prototypes/Entities/Mobs/Species/base.yml | 1 + Resources/Prototypes/Voice/speech_emotes.yml | 4 + .../_Sunrise/Actions/animations.yml | 69 ++++ Resources/keybinds.yml | 3 + 50 files changed, 1372 insertions(+), 239 deletions(-) create mode 100644 Content.Client/Standing/StandingStateSystem.cs create mode 100644 Content.Client/_Sunrise/Animations/EmoteAnimationSystem.cs create mode 100644 Content.Client/_Sunrise/UserActions/Tabs/BaseTabControl.cs create mode 100644 Content.Client/_Sunrise/UserActions/Tabs/EmotesTabControl.cs create mode 100644 Content.Client/_Sunrise/UserActions/Tabs/EmotesTabControl.xaml create mode 100644 Content.Client/_Sunrise/UserActions/Tabs/VerbsTabControl.cs create mode 100644 Content.Client/_Sunrise/UserActions/Tabs/VerbsTabControl.xaml create mode 100644 Content.Client/_Sunrise/UserActions/UserActionUIController.cs create mode 100644 Content.Client/_Sunrise/UserActions/UserActionUISystem.cs create mode 100644 Content.Client/_Sunrise/UserActions/UserActionsPanel.cs create mode 100644 Content.Client/_Sunrise/UserActions/UserActionsPanel.xaml create mode 100644 Content.Server/_Sunrise/Animations/EmoteAnimationSystem.cs create mode 100644 Content.Shared/Standing/SharedStandingStateSystem.cs create mode 100644 Content.Shared/Standing/StandingStateEvents.cs delete mode 100644 Content.Shared/Standing/StandingStateSystem.cs create mode 100644 Content.Shared/_Sunrise/Animations/EmoteAnimationComponent.cs create mode 100644 Resources/Locale/en-US/_strings/_sunrise/user-action-control/user-action-control.ftl create mode 100644 Resources/Locale/ru-RU/_strings/_sunrise/user-action-control/user-action-control.ftl create mode 100644 Resources/Prototypes/_Sunrise/Actions/animations.yml diff --git a/Content.Client/Buckle/BuckleSystem.cs b/Content.Client/Buckle/BuckleSystem.cs index 035e1300ca..2a805346b5 100644 --- a/Content.Client/Buckle/BuckleSystem.cs +++ b/Content.Client/Buckle/BuckleSystem.cs @@ -65,7 +65,6 @@ internal sealed class BuckleSystem : SharedBuckleSystem !buckled || args.Sprite == null) { - _rotationVisualizerSystem.SetHorizontalAngle((uid, rotVisuals), rotVisuals.DefaultRotation); return; } diff --git a/Content.Client/Chat/UI/EmotesMenu.xaml.cs b/Content.Client/Chat/UI/EmotesMenu.xaml.cs index f3b7837f21..8a1e614018 100644 --- a/Content.Client/Chat/UI/EmotesMenu.xaml.cs +++ b/Content.Client/Chat/UI/EmotesMenu.xaml.cs @@ -35,7 +35,7 @@ public sealed partial class EmotesMenu : RadialMenu foreach (var emote in emotes) { var player = _playerManager.LocalSession?.AttachedEntity; - if (emote.Category == EmoteCategory.Invalid || + if (emote.Category == EmoteCategory.Invalid || emote.Category == EmoteCategory.Verb || emote.ChatTriggers.Count == 0 || !(player.HasValue && whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) || whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value)) diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 6abdaeea2d..6f50ce44e2 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -86,6 +86,10 @@ namespace Content.Client.Input human.AddFunction(ContentKeyFunctions.Arcade2); human.AddFunction(ContentKeyFunctions.Arcade3); + // Sunrise + human.AddFunction(ContentKeyFunctions.ToggleStanding); + // Sunrise + // 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 88df46aaea..707f2e16df 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -187,6 +187,10 @@ namespace Content.Client.Options.UI.Tabs AddButton(ContentKeyFunctions.RotateStoredItem); AddButton(ContentKeyFunctions.SaveItemLocation); + // Sunrise + AddButton(ContentKeyFunctions.ToggleStanding); + // Sunrise + AddHeader("ui-options-header-interaction-adv"); AddButton(ContentKeyFunctions.SmartEquipBackpack); AddButton(ContentKeyFunctions.SmartEquipBelt); diff --git a/Content.Client/Standing/StandingStateSystem.cs b/Content.Client/Standing/StandingStateSystem.cs new file mode 100644 index 0000000000..f1fd32c1d8 --- /dev/null +++ b/Content.Client/Standing/StandingStateSystem.cs @@ -0,0 +1,67 @@ +using Content.Shared.Input; +using Content.Shared.Rotation; +using Content.Shared.Standing; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Input.Binding; +using Robust.Shared.Player; + +namespace Content.Client.Standing; + +public sealed class StandingStateSystem : SharedStandingStateSystem +{ + [Dependency] private readonly AppearanceSystem _appearance = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + + public override void Initialize() + { + base.Initialize(); + + CommandBinds.Builder + .Bind(ContentKeyFunctions.ToggleStanding, InputCmdHandler.FromDelegate(ToggleStanding)) + .Register(); + + SubscribeLocalEvent(OnMove); + } + + private void ToggleStanding(ICommonSession? session) + { + if (session is not { AttachedEntity: { Valid: true } uid } _ + || !Exists(uid)) + return; + + RaiseNetworkEvent(new ChangeLayingDownEvent()); + } + + private void OnMove(EntityUid uid, RotationVisualsComponent component, ref MoveEvent args) + { + if (!TryComp(uid, out var sprite) || + !TryComp(uid, out var appearance) || + !TryComp(uid, out var transform) || + component.DefaultRotation == 0) + return; + + _appearance.TryGetData(uid, RotationVisuals.RotationState, out var state, appearance); + + var rotation = transform.LocalRotation + _eyeManager.CurrentEye.Rotation; + if (rotation.GetDir() is Direction.East or Direction.North or Direction.NorthEast or Direction.SouthEast) + { + if (state != RotationState.Horizontal || + sprite.Rotation != component.DefaultRotation) + return; + + component.HorizontalRotation = Angle.FromDegrees(270); + sprite.Rotation = Angle.FromDegrees(270); + + return; + } + + if (state != RotationState.Horizontal || + sprite.Rotation != Angle.FromDegrees(270)) + return; + + component.HorizontalRotation = component.DefaultRotation; + + sprite.Rotation = component.DefaultRotation; + } +} diff --git a/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml b/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml index 653302fae4..65c75ac78f 100644 --- a/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml +++ b/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml @@ -10,6 +10,7 @@ xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client" xmlns:inventory="clr-namespace:Content.Client.UserInterface.Systems.Inventory.Widgets" + xmlns:us="clr-namespace:Content.Client._Sunrise.UserActions" Name="SeparatedChatHud" VerticalExpand="False" VerticalAlignment="Bottom" @@ -33,6 +34,7 @@ + diff --git a/Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs b/Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs index 7b86859a1a..ab946f524a 100644 --- a/Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs +++ b/Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs @@ -21,7 +21,7 @@ public sealed class EmotesUIController : UIController, IOnStateChanged UIManager.GetActiveUIWidgetOrNull()?.EmotesButton; + private MenuButton? EmotesButton => null; private EmotesMenu? _menu; public void OnStateEntered(GameplayState state) diff --git a/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml b/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml index dc8972970a..79f0ce75c0 100644 --- a/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml +++ b/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml @@ -43,6 +43,7 @@ HorizontalExpand="True" AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}" /> + > _emoteList = new(); + + public const string AnimationKey = "emoteAnimationKeyId"; + private const string AnimationKeyTurn = "emoteAnimationKeyId_rotate"; + + public override void Initialize() + { + SubscribeLocalEvent(OnHandleState); + + _emoteList.Add("EmoteFlip", uid => + { + if (_animationSystem.HasRunningAnimation(uid, AnimationKey)) + return; + + var baseAngle = Angle.Zero; + if (EntityManager.TryGetComponent(uid, out SpriteComponent? sprite)) + { + baseAngle = sprite.Rotation; + } + + var animation = new Animation + { + Length = TimeSpan.FromMilliseconds(500), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Rotation), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(baseAngle.Degrees - 10), 0f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(baseAngle.Degrees + 180), 0.25f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(baseAngle.Degrees + 360), 0.25f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(baseAngle.Degrees), 0f), + } + } + } + }; + + _animationSystem.Play(uid, animation, AnimationKey); + }); + + _emoteList.Add("EmoteJump", uid => + { + if (_animationSystem.HasRunningAnimation(uid, AnimationKey)) + return; + + var animation = new Animation + { + Length = TimeSpan.FromMilliseconds(250), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Offset), + InterpolationMode = AnimationInterpolationMode.Cubic, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(Vector2.Zero, 0f), + new AnimationTrackProperty.KeyFrame(new Vector2(0, 0.7f), 0.125f), + new AnimationTrackProperty.KeyFrame(Vector2.Zero, 0.125f), + } + } + } + }; + + _animationSystem.Play(uid, animation, AnimationKey); + }); + + _emoteList.Add("EmoteTurn", uid => + { + if (_animationSystem.HasRunningAnimation(uid, AnimationKeyTurn)) + return; + + var animation = new Animation + { + Length = TimeSpan.FromMilliseconds(900), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(TransformComponent), + Property = nameof(TransformComponent.LocalRotation), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(90), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(180), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(270), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.Zero, 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(90), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(180), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(270), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.Zero, 0.075f), + } + } + } + }; + + _animationSystem.Play(uid, animation, AnimationKeyTurn); + }); + } + + private void OnHandleState(EntityUid uid, EmoteAnimationComponent component, ref ComponentHandleState args) + { + if (args.Current is not EmoteAnimationComponent.EmoteAnimationComponentState state) + return; + + component.AnimationId = state.AnimationId; + if (_emoteList.TryGetValue(component.AnimationId, out var value)) + { + value.Invoke(uid); + } + } +} diff --git a/Content.Client/_Sunrise/UserActions/Tabs/BaseTabControl.cs b/Content.Client/_Sunrise/UserActions/Tabs/BaseTabControl.cs new file mode 100644 index 0000000000..ecd8d9658e --- /dev/null +++ b/Content.Client/_Sunrise/UserActions/Tabs/BaseTabControl.cs @@ -0,0 +1,9 @@ +using Robust.Client.UserInterface; + +namespace Content.Client._Sunrise.UserActions.Tabs; + +[Virtual] +public class BaseTabControl : Control +{ + public virtual bool UpdateState() { return true; } +} diff --git a/Content.Client/_Sunrise/UserActions/Tabs/EmotesTabControl.cs b/Content.Client/_Sunrise/UserActions/Tabs/EmotesTabControl.cs new file mode 100644 index 0000000000..2e5f35e079 --- /dev/null +++ b/Content.Client/_Sunrise/UserActions/Tabs/EmotesTabControl.cs @@ -0,0 +1,183 @@ +using System.Linq; +using System.Numerics; +using Content.Shared.Chat; +using Content.Shared.Chat.Prototypes; +using Content.Shared.Speech; +using Content.Shared.Whitelist; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Client._Sunrise.UserActions.Tabs; + +[GenerateTypedNameReferences] +public sealed partial class EmotesTabControl : BaseTabControl +{ + [Dependency] private readonly EntityManager _entManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly ISharedPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + private TimeSpan _lastEmoteTime; + private static readonly TimeSpan EmoteCooldown = TimeSpan.FromSeconds(3); + + public EmotesTabControl() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + } + + public override bool UpdateState() + { + EmotesList.RemoveAllChildren(); + + var player = _playerManager.LocalEntity; + if (player is not { Valid: true }) + return false; + + var emotes = _prototypeManager.EnumeratePrototypes() + .Where(emote => IsEmoteAvailable(emote, player.Value)) + .OrderBy(x => x.Category) + .ThenBy(x => x.ID) + .ToList(); + + if (emotes.Count == 0) + return false; + + var currentRow = CreateNewRow(); + EmotesList.AddChild(currentRow); + + foreach (var emote in emotes) + { + var button = CreateEmoteButton(emote); + currentRow.AddChild(button); + } + + UpdateButtonsLayout(); + return true; + } + + private void UpdateButtonsLayout() + { + if (!EmotesList.Children.Any()) + return; + + var maxWidth = Math.Min(300, Width); + var firstRow = EmotesList.Children.First() as BoxContainer; + if (firstRow == null) + return; + + var currentRow = firstRow; + var rowWidth = 0f; + + var allButtons = EmotesList.Children + .OfType() + .SelectMany(row => row.Children.ToList()) + .ToList(); + + foreach (var button in allButtons) + { + button.Parent?.RemoveChild(button); + } + + foreach (var child in EmotesList.Children.Skip(1).ToList()) + { + EmotesList.RemoveChild(child); + } + firstRow.RemoveAllChildren(); + + foreach (var button in allButtons) + { + if (rowWidth + 100 > maxWidth) + { + currentRow = CreateNewRow(); + EmotesList.AddChild(currentRow); + rowWidth = 0; + } + + currentRow.AddChild(button); + rowWidth += 100; + } + } + + private BoxContainer CreateNewRow() + { + return new BoxContainer + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, + HorizontalExpand = true, + }; + } + + private Button CreateEmoteButton(EmotePrototype emote) + { + var container = new BoxContainer + { + Orientation = BoxContainer.LayoutOrientation.Vertical, + HorizontalExpand = true, + MinSize = new Vector2(0, 24), + Margin = new Thickness(1) + }; + + var label = new RichTextLabel + { + HorizontalExpand = true, + VerticalExpand = true, + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + Text = Loc.GetString(emote.Name) + }; + + var button = new Button + { + StyleClasses = { "EmoteButton" }, + HorizontalExpand = true, + MinSize = new Vector2(0, 24), + Margin = new Thickness(1) + }; + + container.AddChild(label); + button.AddChild(container); + + button.OnPressed += _ => OnPlayEmote(new ProtoId(emote.ID)); + + return button; + } + + private bool IsEmoteAvailable(EmotePrototype emote, EntityUid player) + { + var whitelistSystem = _entManager.System(); + + if (emote.Category == EmoteCategory.Invalid || emote.Category == EmoteCategory.Verb || emote.ChatTriggers.Count == 0) + return false; + + if (!whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player) || + whitelistSystem.IsBlacklistPass(emote.Blacklist, player)) + return false; + + if (!emote.Available && + _entManager.TryGetComponent(player, out var speech) && + !speech.AllowedEmotes.Contains(emote.ID)) + return false; + + return true; + } + + private void OnPlayEmote(ProtoId protoId) + { + var currentTime = _gameTiming.CurTime; + if (currentTime - _lastEmoteTime < EmoteCooldown) + return; + + _lastEmoteTime = currentTime; + _entManager.RaisePredictiveEvent(new PlayEmoteMessage(protoId)); + } + + protected override void Resized() + { + UpdateButtonsLayout(); + } +} diff --git a/Content.Client/_Sunrise/UserActions/Tabs/EmotesTabControl.xaml b/Content.Client/_Sunrise/UserActions/Tabs/EmotesTabControl.xaml new file mode 100644 index 0000000000..26a82c3fd1 --- /dev/null +++ b/Content.Client/_Sunrise/UserActions/Tabs/EmotesTabControl.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/Content.Client/_Sunrise/UserActions/Tabs/VerbsTabControl.cs b/Content.Client/_Sunrise/UserActions/Tabs/VerbsTabControl.cs new file mode 100644 index 0000000000..8f2c55d99d --- /dev/null +++ b/Content.Client/_Sunrise/UserActions/Tabs/VerbsTabControl.cs @@ -0,0 +1,184 @@ +using System.Linq; +using System.Numerics; +using Content.Shared.Chat; +using Content.Shared.Chat.Prototypes; +using Content.Shared.Speech; +using Content.Shared.Whitelist; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Client._Sunrise.UserActions.Tabs; + +[GenerateTypedNameReferences] +public sealed partial class VerbsTabControl : BaseTabControl +{ + [Dependency] private readonly EntityManager _entManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly ISharedPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + private TimeSpan _lastVerbTime; + private static readonly TimeSpan VerbCooldown = TimeSpan.FromSeconds(0.5f); + + public VerbsTabControl() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + } + + public override bool UpdateState() + { + VerbsList.RemoveAllChildren(); + + var player = _playerManager.LocalEntity; + if (player is not { Valid: true }) + return false; + + var emotes = _prototypeManager.EnumeratePrototypes() + .Where(emote => IsVerbsAvailable(emote, player.Value)) + .OrderBy(x => x.Category) + .ThenBy(x => x.ID) + .ToList(); + + if (emotes.Count == 0) + return false; + + var currentRow = CreateNewRow(); + VerbsList.AddChild(currentRow); + + foreach (var emote in emotes) + { + var button = CreateVerbButton(emote); + currentRow.AddChild(button); + } + + UpdateButtonsLayout(); + return true; + } + + private void UpdateButtonsLayout() + { + if (!VerbsList.Children.Any()) + return; + + var maxWidth = Math.Min(300, Width); + var firstRow = VerbsList.Children.First() as BoxContainer; + if (firstRow == null) + return; + + var currentRow = firstRow; + var rowWidth = 0f; + + var allButtons = VerbsList.Children + .OfType() + .SelectMany(row => row.Children.ToList()) + .ToList(); + + foreach (var button in allButtons) + { + button.Parent?.RemoveChild(button); + } + + foreach (var child in VerbsList.Children.Skip(1).ToList()) + { + VerbsList.RemoveChild(child); + } + firstRow.RemoveAllChildren(); + + foreach (var button in allButtons) + { + if (rowWidth + 100 > maxWidth) + { + currentRow = CreateNewRow(); + VerbsList.AddChild(currentRow); + rowWidth = 0; + } + + currentRow.AddChild(button); + rowWidth += 100; + } + } + + private BoxContainer CreateNewRow() + { + return new BoxContainer + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, + HorizontalExpand = true, + }; + } + + private Button CreateVerbButton(EmotePrototype emote) + { + var container = new BoxContainer + { + Orientation = BoxContainer.LayoutOrientation.Vertical, + HorizontalExpand = true, + MinSize = new Vector2(0, 24), + Margin = new Thickness(1) + }; + + var label = new RichTextLabel + { + HorizontalExpand = true, + VerticalExpand = true, + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + Text = Loc.GetString(emote.Name) + }; + + var button = new Button + { + StyleClasses = { "EmoteButton" }, + HorizontalExpand = true, + MinSize = new Vector2(0, 24), + Margin = new Thickness(1) + }; + + container.AddChild(label); + button.AddChild(container); + + button.OnPressed += _ => OnPlayVerb(new ProtoId(emote.ID)); + + return button; + } + + + private bool IsVerbsAvailable(EmotePrototype emote, EntityUid player) + { + var whitelistSystem = _entManager.System(); + + if (emote.Category == EmoteCategory.Invalid || emote.Category != EmoteCategory.Verb) + return false; + + if (!whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player) || + whitelistSystem.IsBlacklistPass(emote.Blacklist, player)) + return false; + + if (!emote.Available && + _entManager.TryGetComponent(player, out var speech) && + !speech.AllowedEmotes.Contains(emote.ID)) + return false; + + return true; + } + + private void OnPlayVerb(ProtoId protoId) + { + var currentTime = _gameTiming.CurTime; + if (currentTime - _lastVerbTime < VerbCooldown) + return; + + _lastVerbTime = currentTime; + _entManager.RaisePredictiveEvent(new PlayEmoteMessage(protoId)); + } + + protected override void Resized() + { + UpdateButtonsLayout(); + } +} diff --git a/Content.Client/_Sunrise/UserActions/Tabs/VerbsTabControl.xaml b/Content.Client/_Sunrise/UserActions/Tabs/VerbsTabControl.xaml new file mode 100644 index 0000000000..214c4ffd45 --- /dev/null +++ b/Content.Client/_Sunrise/UserActions/Tabs/VerbsTabControl.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/Content.Client/_Sunrise/UserActions/UserActionUIController.cs b/Content.Client/_Sunrise/UserActions/UserActionUIController.cs new file mode 100644 index 0000000000..e11ce45d44 --- /dev/null +++ b/Content.Client/_Sunrise/UserActions/UserActionUIController.cs @@ -0,0 +1,45 @@ +using Content.Client._Sunrise.UserActions.Tabs; +using Robust.Client.UserInterface.Controllers; + +namespace Content.Client._Sunrise.UserActions; + +public sealed class UserActionUIController : UIController, IOnSystemChanged +{ + private UserActionsPanel? _panel; + private List _tabs = new(); + + public void OnSystemLoaded(UserActionUISystem system) + { + system.PlayerAttachedEvent += OnAttached; + system.PlayerDetachedEvent += OnDetached; + } + + public void OnSystemUnloaded(UserActionUISystem system) + { + system.PlayerAttachedEvent -= OnAttached; + system.PlayerDetachedEvent -= OnDetached; + } + + private void OnAttached() + { + _panel?.UpdateTabs(); + } + + private void OnDetached() + { + _panel?.UpdateTabs(); + } + + public void RegisterPanel(UserActionsPanel panel) + { + _panel = panel; + } + + public void RegisterTab(BaseTabControl tab) + { + if (!_tabs.Contains(tab)) + _tabs.Add(tab); + } + + public List GetTabs() => _tabs; +} diff --git a/Content.Client/_Sunrise/UserActions/UserActionUISystem.cs b/Content.Client/_Sunrise/UserActions/UserActionUISystem.cs new file mode 100644 index 0000000000..a5eef99239 --- /dev/null +++ b/Content.Client/_Sunrise/UserActions/UserActionUISystem.cs @@ -0,0 +1,23 @@ +using Robust.Shared.Player; + +namespace Content.Client._Sunrise.UserActions; + +public sealed class UserActionUISystem : EntitySystem +{ + public event Action? PlayerAttachedEvent; + public event Action? PlayerDetachedEvent; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(LocalPlayerAttached); + SubscribeLocalEvent(LocalPlayerDetached); + } + + private void LocalPlayerAttached(LocalPlayerAttachedEvent ev) + => PlayerAttachedEvent?.Invoke(); + + private void LocalPlayerDetached(LocalPlayerDetachedEvent ev) + => PlayerDetachedEvent?.Invoke(); +} diff --git a/Content.Client/_Sunrise/UserActions/UserActionsPanel.cs b/Content.Client/_Sunrise/UserActions/UserActionsPanel.cs new file mode 100644 index 0000000000..a2912e4bbf --- /dev/null +++ b/Content.Client/_Sunrise/UserActions/UserActionsPanel.cs @@ -0,0 +1,49 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client._Sunrise.UserActions; + +[GenerateTypedNameReferences] +public sealed partial class UserActionsPanel : Control +{ + private UserActionUIController _controller; + + public UserActionsPanel() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + _controller = UserInterfaceManager.GetUIController(); + _controller.RegisterPanel(this); + + RegisterTabs(); + } + + private void RegisterTabs() + { + _controller.RegisterTab(EmotesTabControl); + TabContainer.SetTabTitle(EmotesTabControl, Loc.GetString("user-action-control-tab-emote")); + + _controller.RegisterTab(VerbTabControl); + TabContainer.SetTabTitle(VerbTabControl, Loc.GetString("user-action-control-tab-verbs")); + } + + public void UpdateTabs() + { + var anyVisible = false; + + foreach (var tab in _controller.GetTabs()) + { + var visible = tab.UpdateState(); + TabContainer.SetTabVisible(tab, visible); + + if (visible) + anyVisible = true; + } + + MainPanel.Visible = true; + NoContentLabel.Visible = !anyVisible; + } +} diff --git a/Content.Client/_Sunrise/UserActions/UserActionsPanel.xaml b/Content.Client/_Sunrise/UserActions/UserActionsPanel.xaml new file mode 100644 index 0000000000..2eae165c0b --- /dev/null +++ b/Content.Client/_Sunrise/UserActions/UserActionsPanel.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs index 1b31fe38c2..7a1d5da8e7 100644 --- a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs +++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs @@ -59,7 +59,7 @@ namespace Content.IntegrationTests.Tests.Buckle var entityManager = server.ResolveDependency(); var actionBlocker = entityManager.System(); var buckleSystem = entityManager.System(); - var standingState = entityManager.System(); + var standingState = entityManager.System(); var xformSystem = entityManager.System(); EntityUid human = default; diff --git a/Content.Server/Morgue/CrematoriumSystem.cs b/Content.Server/Morgue/CrematoriumSystem.cs index 656457dc98..a5979a2a27 100644 --- a/Content.Server/Morgue/CrematoriumSystem.cs +++ b/Content.Server/Morgue/CrematoriumSystem.cs @@ -26,7 +26,7 @@ public sealed class CrematoriumSystem : EntitySystem [Dependency] private readonly GhostSystem _ghostSystem = default!; [Dependency] private readonly EntityStorageSystem _entityStorage = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly StandingStateSystem _standing = default!; + [Dependency] private readonly SharedStandingStateSystem _standing = default!; [Dependency] private readonly SharedMindSystem _minds = default!; [Dependency] private readonly SharedContainerSystem _containers = default!; diff --git a/Content.Server/NPC/Systems/NPCCombatSystem.Ranged.cs b/Content.Server/NPC/Systems/NPCCombatSystem.Ranged.cs index d7196ea73c..e77d5f74d6 100644 --- a/Content.Server/NPC/Systems/NPCCombatSystem.Ranged.cs +++ b/Content.Server/NPC/Systems/NPCCombatSystem.Ranged.cs @@ -202,6 +202,7 @@ public sealed partial class NPCCombatSystem return; } + _gun.SetTarget(gun, comp.Target); _gun.AttemptShoot(uid, gunUid, gun, targetCordinates); } } diff --git a/Content.Server/Speech/EmotesMenuSystem.cs b/Content.Server/Speech/EmotesMenuSystem.cs index a69b5a65e4..9306f907e3 100644 --- a/Content.Server/Speech/EmotesMenuSystem.cs +++ b/Content.Server/Speech/EmotesMenuSystem.cs @@ -22,7 +22,7 @@ public sealed partial class EmotesMenuSystem : EntitySystem if (!player.HasValue) return; - if (!_prototypeManager.TryIndex(msg.ProtoId, out var proto) || proto.ChatTriggers.Count == 0) + if (!_prototypeManager.TryIndex(msg.ProtoId, out var proto)) return; _chat.TryEmoteWithChat(player.Value, msg.ProtoId); diff --git a/Content.Server/Standing/StandingStateSystem.cs b/Content.Server/Standing/StandingStateSystem.cs index e2b6495844..59b08511d5 100644 --- a/Content.Server/Standing/StandingStateSystem.cs +++ b/Content.Server/Standing/StandingStateSystem.cs @@ -1,6 +1,10 @@ using System.Numerics; +using Content.Shared.DoAfter; +using Content.Shared.Gravity; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; +using Content.Shared.Humanoid; +using Content.Shared.Mobs.Systems; using Content.Shared.Standing; using Content.Shared.Throwing; using Robust.Shared.Physics.Components; @@ -8,55 +12,60 @@ using Robust.Shared.Random; namespace Content.Server.Standing; -public sealed class StandingStateSystem : EntitySystem +public sealed class StandingStateSystem : SharedStandingStateSystem { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly ThrowingSystem _throwingSystem = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeNetworkEvent(OnChangeState); + SubscribeLocalEvent(FallOver); + } + + private void OnChangeState(ChangeLayingDownEvent msg, EntitySessionEventArgs args) + { + if (args.SenderSession is not { AttachedEntity: { Valid: true } uid } + || !Exists(uid) + || !TryComp(args.SenderSession.AttachedEntity, out var standingStateComponent) + || _gravity.IsWeightless(args.SenderSession.AttachedEntity.Value)) + return; + + if (standingStateComponent.CurrentState == StandingState.Laying) + TryStandUp(uid, standingStateComponent); + else + { + Fall(uid); + } + } 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); - var fellEvent = new FellDownEvent(uid); - RaiseLocalEvent(uid, fellEvent, false); - if (!TryComp(uid, out HandsComponent? handsComp)) return; - var worldRotation = EntityManager.GetComponent(uid).WorldRotation.ToVec(); + var worldRotation = _transform.GetWorldRotation(uid).ToVec(); foreach (var hand in handsComp.Hands.Values) { - if (hand.HeldEntity is not EntityUid held) + if (hand.HeldEntity is not { } held) + continue; + if (!_handsSystem.TryDrop(uid, hand, checkActionBlocker: false, handsComp: handsComp)) continue; - if (!_handsSystem.TryDrop(uid, hand, null, checkActionBlocker: false, handsComp: handsComp)) - continue; - - _throwingSystem.TryThrow(held, + _throwingSystem.TryThrow( + held, _random.NextAngle().RotateVec(direction / dropAngle + worldRotation / 50), 0.5f * dropAngle * _random.NextFloat(-0.9f, 1.1f), - uid, 0); + uid, + 0); } } - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(FallOver); - } - } - - /// - /// Raised after an entity falls down. - /// - public sealed class FellDownEvent : EntityEventArgs - { - public EntityUid Uid { get; } - public FellDownEvent(EntityUid uid) - { - Uid = uid; - } - } diff --git a/Content.Server/_Sunrise/Animations/EmoteAnimationSystem.cs b/Content.Server/_Sunrise/Animations/EmoteAnimationSystem.cs new file mode 100644 index 0000000000..5f6695c5fe --- /dev/null +++ b/Content.Server/_Sunrise/Animations/EmoteAnimationSystem.cs @@ -0,0 +1,52 @@ +using Content.Server.Chat.Systems; +using Content.Shared._Sunrise.Animations; +using Content.Shared.Chat.Prototypes; +using Content.Shared.Gravity; +using Content.Shared.Standing; +using Robust.Shared.GameStates; + +namespace Content.Server._Sunrise.Animations; + +public sealed class EmoteAnimationSystem : EntitySystem +{ + [Dependency] private readonly SharedStandingStateSystem _sharedStanding = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnGetState); + SubscribeLocalEvent(OnEmote); + } + + private void OnGetState(EntityUid uid, EmoteAnimationComponent component, ref ComponentGetState args) + { + args.State = new EmoteAnimationComponent.EmoteAnimationComponentState(component.AnimationId); + } + + private void OnEmote(EntityUid uid, EmoteAnimationComponent component, ref EmoteEvent args) + { + if (args.Handled || !args.Emote.Category.HasFlag(EmoteCategory.Verb)) + return; + + PlayEmoteAnimation(uid, component, args.Emote.ID); + } + + public void PlayEmoteAnimation(EntityUid uid, EmoteAnimationComponent component, string emoteId) + { + if (emoteId == "EmoteLay") + { + if (_gravity.IsWeightless(uid)) + return; + + if (_sharedStanding.IsDown(uid)) + _sharedStanding.TryStandUp(uid); + else + _sharedStanding.TryLieDown(uid); + + return; + } + + component.AnimationId = emoteId; + Dirty(uid, component); + } +} diff --git a/Content.Server/_Sunrise/Carrying/CarryingSystem.cs b/Content.Server/_Sunrise/Carrying/CarryingSystem.cs index cc323b94bf..f4e087358c 100644 --- a/Content.Server/_Sunrise/Carrying/CarryingSystem.cs +++ b/Content.Server/_Sunrise/Carrying/CarryingSystem.cs @@ -41,7 +41,7 @@ namespace Content.Server._Sunrise.Carrying [Dependency] private readonly SharedVirtualItemSystem _virtualItemSystem = default!; [Dependency] private readonly CarryingSlowdownSystem _slowdown = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly StandingStateSystem _standingState = default!; + [Dependency] private readonly SharedStandingStateSystem _standingState = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly PullingSystem _pullingSystem = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; diff --git a/Content.Server/_Sunrise/Footprints/FootPrintsSystem.cs b/Content.Server/_Sunrise/Footprints/FootPrintsSystem.cs index f3ae3b0a6c..78f3a786c6 100644 --- a/Content.Server/_Sunrise/Footprints/FootPrintsSystem.cs +++ b/Content.Server/_Sunrise/Footprints/FootPrintsSystem.cs @@ -29,7 +29,7 @@ public sealed class FootprintSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedMapSystem _mapSystem = default!; - [Dependency] private readonly StandingStateSystem _standingStateSystem = default!; + [Dependency] private readonly SharedStandingStateSystem _standingStateSystem = default!; #endregion #region Entity Queries diff --git a/Content.Server/_Sunrise/Footprints/PuddleFootPrintsSystem.cs b/Content.Server/_Sunrise/Footprints/PuddleFootPrintsSystem.cs index 9946e28966..7fb8fbd578 100644 --- a/Content.Server/_Sunrise/Footprints/PuddleFootPrintsSystem.cs +++ b/Content.Server/_Sunrise/Footprints/PuddleFootPrintsSystem.cs @@ -5,8 +5,8 @@ using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Fluids.Components; +using Content.Shared.Standing; using Robust.Shared.Physics.Events; -using StandingStateSystem = Content.Shared.Standing.StandingStateSystem; namespace Content.Server._Sunrise.Footprints; @@ -16,7 +16,7 @@ namespace Content.Server._Sunrise.Footprints; public sealed class PuddleFootprintSystem : EntitySystem { [Dependency] private readonly SharedSolutionContainerSystem _solutionSystem = default!; - [Dependency] private readonly StandingStateSystem _standingStateSystem = default!; + [Dependency] private readonly SharedStandingStateSystem _standingStateSystem = default!; /// public override void Initialize() diff --git a/Content.Shared/Body/Systems/SharedBodySystem.cs b/Content.Shared/Body/Systems/SharedBodySystem.cs index a45966fcc3..711c7a3d9d 100644 --- a/Content.Shared/Body/Systems/SharedBodySystem.cs +++ b/Content.Shared/Body/Systems/SharedBodySystem.cs @@ -34,7 +34,7 @@ public abstract partial class SharedBodySystem : EntitySystem [Dependency] protected readonly MovementSpeedModifierSystem Movement = default!; [Dependency] protected readonly SharedContainerSystem Containers = default!; [Dependency] protected readonly SharedTransformSystem SharedTransform = default!; - [Dependency] protected readonly StandingStateSystem Standing = default!; + [Dependency] protected readonly SharedStandingStateSystem Standing = default!; public override void Initialize() { diff --git a/Content.Shared/Buckle/SharedBuckleSystem.cs b/Content.Shared/Buckle/SharedBuckleSystem.cs index da1d111f97..7b0b2de668 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.cs @@ -32,7 +32,7 @@ public abstract partial class SharedBuckleSystem : EntitySystem [Dependency] private readonly SharedJointSystem _joints = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly StandingStateSystem _standing = default!; + [Dependency] private readonly SharedStandingStateSystem _standing = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedRotationVisualsSystem _rotationVisuals = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; diff --git a/Content.Shared/Chat/Prototypes/EmotePrototype.cs b/Content.Shared/Chat/Prototypes/EmotePrototype.cs index 7ee958ee6a..2f19fc324c 100644 --- a/Content.Shared/Chat/Prototypes/EmotePrototype.cs +++ b/Content.Shared/Chat/Prototypes/EmotePrototype.cs @@ -81,5 +81,6 @@ public enum EmoteCategory : byte Invalid = 0, Vocal = 1 << 0, Hands = 1 << 1, + Verb = 1 << 2, General = byte.MaxValue } diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index 4191e05530..9039e27577 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -62,6 +62,10 @@ namespace Content.Shared.Input public static readonly BoundKeyFunction ZoomIn = "ZoomIn"; public static readonly BoundKeyFunction ResetZoom = "ResetZoom"; + // Sunrise + public static readonly BoundKeyFunction ToggleStanding = "ToggleStanding"; + // Sunrise + public static readonly BoundKeyFunction ArcadeUp = "ArcadeUp"; public static readonly BoundKeyFunction ArcadeDown = "ArcadeDown"; public static readonly BoundKeyFunction ArcadeLeft = "ArcadeLeft"; diff --git a/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs b/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs index f6ce235ff7..e0783f624a 100644 --- a/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs +++ b/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs @@ -19,7 +19,7 @@ namespace Content.Shared.Medical.Cryogenics; public abstract partial class SharedCryoPodSystem: EntitySystem { [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; - [Dependency] private readonly StandingStateSystem _standingStateSystem = default!; + [Dependency] private readonly SharedStandingStateSystem _standingStateSystem = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedContainerSystem _containerSystem = default!; diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs index f99bd43feb..a8b0a15844 100644 --- a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs +++ b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs @@ -104,13 +104,13 @@ public partial class MobStateSystem _appearance.SetData(target, MobStateVisuals.State, MobState.Alive); break; case MobState.Critical: - _standing.Down(target); _appearance.SetData(target, MobStateVisuals.State, MobState.Critical); + _standing.Down(target); break; case MobState.Dead: EnsureComp(target); - _standing.Down(target); _appearance.SetData(target, MobStateVisuals.State, MobState.Dead); + _standing.Down(target); break; case MobState.Invalid: //unused; diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.cs b/Content.Shared/Mobs/Systems/MobStateSystem.cs index 22e376afe4..adc6858665 100644 --- a/Content.Shared/Mobs/Systems/MobStateSystem.cs +++ b/Content.Shared/Mobs/Systems/MobStateSystem.cs @@ -13,7 +13,7 @@ public partial class MobStateSystem : EntitySystem { [Dependency] private readonly ActionBlockerSystem _blocker = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly StandingStateSystem _standing = default!; + [Dependency] private readonly SharedStandingStateSystem _standing = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly IGameTiming _timing = default!; diff --git a/Content.Shared/Morgue/EntityStorageLayingDownOverrideSystem.cs b/Content.Shared/Morgue/EntityStorageLayingDownOverrideSystem.cs index 9341168ba3..acdced0213 100644 --- a/Content.Shared/Morgue/EntityStorageLayingDownOverrideSystem.cs +++ b/Content.Shared/Morgue/EntityStorageLayingDownOverrideSystem.cs @@ -7,7 +7,7 @@ namespace Content.Shared.Morgue; public sealed class EntityStorageLayingDownOverrideSystem : EntitySystem { - [Dependency] private readonly StandingStateSystem _standing = default!; + [Dependency] private readonly SharedStandingStateSystem _standing = default!; public override void Initialize() { diff --git a/Content.Shared/Standing/SharedStandingStateSystem.cs b/Content.Shared/Standing/SharedStandingStateSystem.cs new file mode 100644 index 0000000000..f85908ec23 --- /dev/null +++ b/Content.Shared/Standing/SharedStandingStateSystem.cs @@ -0,0 +1,316 @@ +using Content.Shared.ActionBlocker; +using Content.Shared.Buckle; +using Content.Shared.Buckle.Components; +using Content.Shared.Damage.Systems; +using Content.Shared.DoAfter; +using Content.Shared.Hands.Components; +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.Stunnable; +using Content.Shared.Throwing; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Network; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; + +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 StaminaSystem _stamina = default!; + [Dependency] private readonly SharedStunSystem _stun = default!; + [Dependency] private readonly ThrowingSystem _throwing = default!; + [Dependency] private readonly ActionBlockerSystem _blocker = default!; + + private const int StandingCollisionLayer = (int) CollisionGroup.MidImpassable; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStandUpDoAfter); + SubscribeLocalEvent(OnDownDoAfter); + SubscribeLocalEvent(OnMove); + SubscribeLocalEvent(OnRefreshMovementSpeed); + } + + #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 = true + }; + + 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 + }; + + return _doAfter.TryStartDoAfter(args); + } + + public void Fall(EntityUid uid, float rollDistance = 3f, float rollSpeed = 6f) + { + if (!TryComp(uid, out var physics)) + return; + + var velocity = physics.LinearVelocity; + if (velocity.LengthSquared() < 0.1f) + { + Down(uid, dropHeldItems: false); + return; + } + + var direction = velocity.Normalized(); + + Down(uid, dropHeldItems: false); + _stun.TryStun(uid, TimeSpan.FromSeconds(1.3f), true); + _stamina.TakeStaminaDamage(uid, 20); + + _throwing.TryThrow( + uid, + direction * rollDistance, + rollSpeed, + friction: 5f, + compensateFriction: true, + animated: false, + playSound: true, + doSpin: false + ); + } + + 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 +} diff --git a/Content.Shared/Standing/StandingStateComponent.cs b/Content.Shared/Standing/StandingStateComponent.cs index 0270872235..8413791668 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; +using Robust.Shared.Serialization; -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, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier DownSound { get; set; } = new SoundCollectionSpecifier("BodyFall"); - [DataField, AutoNetworkedField] - public bool Standing { get; set; } = true; + [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadOnly)] + public StandingState CurrentState = StandingState.Standing; - /// - /// 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(); - } + [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadOnly)] + public List ChangedFixtures = new(); + + [DataField, AutoNetworkedField] + public float CycleTime { get; set; } = 1f; + + [DataField, AutoNetworkedField] + public float BaseSpeedModify { get; set; } = 0.4f; +} + +[Serializable, NetSerializable] +public enum StandingState : byte +{ + Standing = 0, + Laying = 1 } diff --git a/Content.Shared/Standing/StandingStateEvents.cs b/Content.Shared/Standing/StandingStateEvents.cs new file mode 100644 index 0000000000..29a9b55184 --- /dev/null +++ b/Content.Shared/Standing/StandingStateEvents.cs @@ -0,0 +1,28 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Standing; + +[Serializable, NetSerializable] +public sealed class DropHandItemsEvent : EventArgs; + +[Serializable, NetSerializable] +public sealed class DownAttemptEvent : CancellableEntityEventArgs; + +[Serializable, NetSerializable] +public sealed class StandAttemptEvent : CancellableEntityEventArgs; + +[Serializable, NetSerializable] +public sealed class StoodEvent : EntityEventArgs; + +[Serializable, NetSerializable] +public sealed class DownedEvent : EntityEventArgs; + +[Serializable, NetSerializable] +public sealed class ChangeLayingDownEvent : CancellableEntityEventArgs; + +[Serializable, NetSerializable] +public sealed partial class StandUpDoAfterEvent : SimpleDoAfterEvent; + +[Serializable, NetSerializable] +public sealed partial class DownDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/Standing/StandingStateSystem.cs b/Content.Shared/Standing/StandingStateSystem.cs deleted file mode 100644 index 8d9be9ab77..0000000000 --- a/Content.Shared/Standing/StandingStateSystem.cs +++ /dev/null @@ -1,172 +0,0 @@ -using Content.Shared.Hands.Components; -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 -{ - public sealed class StandingStateSystem : EntitySystem - { - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedPhysicsSystem _physics = 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; - - public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null) - { - if (!Resolve(uid, ref standingState, false)) - return false; - - return !standingState.Standing; - } - - public bool Down(EntityUid uid, - bool playSound = true, - bool dropHeldItems = true, - bool force = false, - StandingStateComponent? standingState = null, - AppearanceComponent? appearance = null, - HandsComponent? hands = null) - { - // TODO: This should actually log missing comps... - if (!Resolve(uid, ref standingState, false)) - return false; - - // Optional component. - Resolve(uid, ref appearance, ref hands, false); - - if (!standingState.Standing) - return true; - - // 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 (!force) - { - var msg = new DownAttemptEvent(); - RaiseLocalEvent(uid, msg, false); - - if (msg.Cancelled) - return false; - } - - standingState.Standing = false; - Dirty(uid, standingState); - RaiseLocalEvent(uid, new DownedEvent(), false); - - // Seemed like the best place to put it - _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Horizontal, appearance); - - // 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); - } - } - - // 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; - - if (playSound) - { - _audio.PlayPredicted(standingState.DownSound, uid, uid); - } - - 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; - - // Optional component. - Resolve(uid, ref appearance, false); - - if (standingState.Standing) - return true; - - if (!force) - { - var msg = new StandAttemptEvent(); - RaiseLocalEvent(uid, msg, false); - - if (msg.Cancelled) - return false; - } - - standingState.Standing = true; - 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) - { - if (fixtureComponent.Fixtures.TryGetValue(key, out var fixture)) - _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask | StandingCollisionLayer, fixtureComponent); - } - } - standingState.ChangedFixtures.Clear(); - - return true; - } - } - - public sealed class DropHandItemsEvent : EventArgs - { - } - - /// - /// Subscribe if you can potentially block a down attempt. - /// - public sealed class DownAttemptEvent : CancellableEntityEventArgs - { - } - - /// - /// Subscribe if you can potentially block a stand attempt. - /// - public sealed class StandAttemptEvent : CancellableEntityEventArgs - { - } - - /// - /// 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 8f828131f5..42bb6b4466 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.cs @@ -30,7 +30,7 @@ public abstract class SharedStunSystem : EntitySystem [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 SharedStandingStateSystem _standingState = default!; [Dependency] private readonly StatusEffectsSystem _statusEffect = default!; /// diff --git a/Content.Shared/Traits/Assorted/LegsParalyzedSystem.cs b/Content.Shared/Traits/Assorted/LegsParalyzedSystem.cs index 929ee3a19f..641542dd8e 100644 --- a/Content.Shared/Traits/Assorted/LegsParalyzedSystem.cs +++ b/Content.Shared/Traits/Assorted/LegsParalyzedSystem.cs @@ -10,7 +10,7 @@ namespace Content.Shared.Traits.Assorted; public sealed class LegsParalyzedSystem : EntitySystem { [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifierSystem = default!; - [Dependency] private readonly StandingStateSystem _standingSystem = default!; + [Dependency] private readonly SharedStandingStateSystem _standingSystem = default!; [Dependency] private readonly SharedBodySystem _bodySystem = default!; public override void Initialize() diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 460a2c5254..817cbcba19 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -249,6 +249,11 @@ public abstract partial class SharedGunSystem : EntitySystem EntityManager.DirtyField(uid, gun, nameof(GunComponent.ShotCounter)); } + public void SetTarget(GunComponent gun, EntityUid target) + { + gun.Target = target; + } + /// /// Attempts to shoot at the target coordinates. Resets the shot counter after every shot. /// diff --git a/Content.Shared/_Sunrise/Animations/EmoteAnimationComponent.cs b/Content.Shared/_Sunrise/Animations/EmoteAnimationComponent.cs new file mode 100644 index 0000000000..54d900aa34 --- /dev/null +++ b/Content.Shared/_Sunrise/Animations/EmoteAnimationComponent.cs @@ -0,0 +1,29 @@ +using Content.Shared.Actions; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared._Sunrise.Animations; + +[RegisterComponent, NetworkedComponent] +public sealed partial class EmoteAnimationComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite)] + public string AnimationId = "none"; + + [Serializable, NetSerializable] + public partial class EmoteAnimationComponentState : ComponentState + { + public string AnimationId { get; init; } + + public EmoteAnimationComponentState(string animationId) + { + AnimationId = animationId; + } + } +} + +public sealed partial class EmoteActionEvent : InstantActionEvent +{ + [ViewVariables, DataField("emote", readOnly: true, required: true)] + public string Emote = default!; +}; diff --git a/Content.Shared/_Sunrise/Medical/Surgery/SharedSurgerySystem.cs b/Content.Shared/_Sunrise/Medical/Surgery/SharedSurgerySystem.cs index c85da147e3..d94ce70814 100644 --- a/Content.Shared/_Sunrise/Medical/Surgery/SharedSurgerySystem.cs +++ b/Content.Shared/_Sunrise/Medical/Surgery/SharedSurgerySystem.cs @@ -36,7 +36,7 @@ public abstract partial class SharedSurgerySystem : EntitySystem [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly IPrototypeManager _prototypes = default!; [Dependency] private readonly RotateToFaceSystem _rotateToFace = default!; - [Dependency] private readonly StandingStateSystem _standing = default!; + [Dependency] private readonly SharedStandingStateSystem _standing = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedBodySystem _body = default!; [Dependency] private readonly IReflectionManager _reflectionManager = default!; diff --git a/Resources/Locale/en-US/_strings/_sunrise/user-action-control/user-action-control.ftl b/Resources/Locale/en-US/_strings/_sunrise/user-action-control/user-action-control.ftl new file mode 100644 index 0000000000..0ed32843bb --- /dev/null +++ b/Resources/Locale/en-US/_strings/_sunrise/user-action-control/user-action-control.ftl @@ -0,0 +1,3 @@ +user-action-control-nothing = Nothing to display here yet. +user-action-control-tab-emote = Emotes +user-action-control-tab-verbs = Verbs diff --git a/Resources/Locale/ru-RU/_strings/_sunrise/user-action-control/user-action-control.ftl b/Resources/Locale/ru-RU/_strings/_sunrise/user-action-control/user-action-control.ftl new file mode 100644 index 0000000000..41cf08603c --- /dev/null +++ b/Resources/Locale/ru-RU/_strings/_sunrise/user-action-control/user-action-control.ftl @@ -0,0 +1,3 @@ +user-action-control-nothing = Пока нечего отображать. +user-action-control-tab-emote = Эмоции +user-action-control-tab-verbs = Действия diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index f3498d9c37..4874352a03 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -249,6 +249,7 @@ - type: Carriable - type: CanEscapeInventory - type: Mood + - type: EmoteAnimation # Sunrise-End - type: Barotrauma damage: diff --git a/Resources/Prototypes/Voice/speech_emotes.yml b/Resources/Prototypes/Voice/speech_emotes.yml index b456a178ff..c01c14b17e 100644 --- a/Resources/Prototypes/Voice/speech_emotes.yml +++ b/Resources/Prototypes/Voice/speech_emotes.yml @@ -253,6 +253,7 @@ - Hands blacklist: components: + - Ghost - BorgChassis chatMessages: ["chat-emote-msg-clap"] chatTriggers: @@ -281,6 +282,7 @@ blacklist: components: - BorgChassis + - Ghost chatMessages: ["chat-emote-msg-snap"] # snaps <{THEIR($ent)}> fingers? chatTriggers: - snap @@ -319,6 +321,7 @@ blacklist: components: - BorgChassis + - Ghost chatMessages: ["chat-emote-msg-thump"] chatTriggers: - thump @@ -343,6 +346,7 @@ blacklist: components: - BorgChassis + - Ghost chatMessages: ["chat-emote-msg-salute"] chatTriggers: - salute diff --git a/Resources/Prototypes/_Sunrise/Actions/animations.yml b/Resources/Prototypes/_Sunrise/Actions/animations.yml new file mode 100644 index 0000000000..4df3268b22 --- /dev/null +++ b/Resources/Prototypes/_Sunrise/Actions/animations.yml @@ -0,0 +1,69 @@ +- type: emote + id: EmoteFlip + category: Verb + name: Сальто + whitelist: + components: + - Hands + blacklist: + components: + - Ghost + - BorgChassis + chatMessages: [делает сальто] + chatTriggers: + - сделал сальто + - сделала сальто + - делает сальто + - устроила сальто + - устроил сальто + +- type: emote + id: EmoteJump + category: Verb + name: Прыгнуть + whitelist: + components: + - Hands + blacklist: + components: + - Ghost + - BorgChassis + chatMessages: [прыгает] + chatTriggers: + - прыгает + - прыгнула + - прыгнул + - подпрыгнул + - подпрыгнула + - подскакивает + +- type: emote + id: EmoteTurn + category: Verb + name: Танцевать + whitelist: + components: + - Hands + blacklist: + components: + - Ghost + - BorgChassis + chatMessages: [танцует] + chatTriggers: + - танцует + - кружится + - оборачивается + - покружилась + - покружился + +- type: emote + id: EmoteLay + category: Verb + name: Лечь/Встать + whitelist: + components: + - Hands + blacklist: + components: + - Ghost + - BorgChassis diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 5c5c28e7be..5d842bc551 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -587,3 +587,6 @@ binds: - function: CockGun # Sunrise-Edit type: State key: C +- function: ToggleStanding + type: State + key: R