Новая панель эмоутов. (#891)

* wacky

* work

* work

* work

* work

* eff

* a
This commit is contained in:
haiwwkes 2024-12-25 01:27:09 +05:00 committed by GitHub
parent d8547bf089
commit 5192d120ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 1372 additions and 239 deletions

View file

@ -65,7 +65,6 @@ internal sealed class BuckleSystem : SharedBuckleSystem
!buckled ||
args.Sprite == null)
{
_rotationVisualizerSystem.SetHorizontalAngle((uid, rotVisuals), rotVisuals.DefaultRotation);
return;
}

View file

@ -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))

View file

@ -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);

View file

@ -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);

View file

@ -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<SharedStandingStateSystem>();
SubscribeLocalEvent<RotationVisualsComponent, MoveEvent>(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<SpriteComponent>(uid, out var sprite) ||
!TryComp<AppearanceComponent>(uid, out var appearance) ||
!TryComp<TransformComponent>(uid, out var transform) ||
component.DefaultRotation == 0)
return;
_appearance.TryGetData<RotationState>(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;
}
}

View file

@ -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 @@
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SeparationOverride="10" Margin="10">
<menuBar:GameTopMenuBar Name="TopBar" HorizontalExpand="True" Access="Protected" />
<us:UserActionsPanel></us:UserActionsPanel>
<chat:ChatBox VerticalExpand="True" HorizontalExpand="True" Name="Chat" Access="Protected" MinSize="0 0"/>
</BoxContainer>
</PanelContainer>

View file

@ -21,7 +21,7 @@ public sealed class EmotesUIController : UIController, IOnStateChanged<GameplayS
[Dependency] private readonly IClyde _displayManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
private MenuButton? EmotesButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.EmotesButton;
private MenuButton? EmotesButton => null;
private EmotesMenu? _menu;
public void OnStateEntered(GameplayState state)

View file

@ -43,6 +43,7 @@
HorizontalExpand="True"
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
/>
<!-- not needed because of new emote ui system
<ui:MenuButton
Name="EmotesButton"
Access="Internal"
@ -53,6 +54,7 @@
HorizontalExpand="True"
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
/>
-->
<ui:MenuButton
Name="CraftingButton"
Access="Internal"

View file

@ -0,0 +1,132 @@
using System.Numerics;
using Content.Shared._Sunrise.Animations;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
using Robust.Shared.GameStates;
namespace Content.Client._Sunrise.Animations;
public sealed class EmoteAnimationSystem : EntitySystem
{
[Dependency] private readonly AnimationPlayerSystem _animationSystem = default!;
private readonly Dictionary<string, Action<EntityUid>> _emoteList = new();
public const string AnimationKey = "emoteAnimationKeyId";
private const string AnimationKeyTurn = "emoteAnimationKeyId_rotate";
public override void Initialize()
{
SubscribeLocalEvent<EmoteAnimationComponent, ComponentHandleState>(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);
}
}
}

View file

@ -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; }
}

View file

@ -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<EmotePrototype>()
.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<BoxContainer>()
.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<EmotePrototype>(emote.ID));
return button;
}
private bool IsEmoteAvailable(EmotePrototype emote, EntityUid player)
{
var whitelistSystem = _entManager.System<EntityWhitelistSystem>();
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<SpeechComponent>(player, out var speech) &&
!speech.AllowedEmotes.Contains(emote.ID))
return false;
return true;
}
private void OnPlayEmote(ProtoId<EmotePrototype> protoId)
{
var currentTime = _gameTiming.CurTime;
if (currentTime - _lastEmoteTime < EmoteCooldown)
return;
_lastEmoteTime = currentTime;
_entManager.RaisePredictiveEvent(new PlayEmoteMessage(protoId));
}
protected override void Resized()
{
UpdateButtonsLayout();
}
}

View file

@ -0,0 +1,20 @@
<controls:EmotesTabControl
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client._Sunrise.UserActions.Tabs">
<PanelContainer StyleClasses="BackgroundDark">
<ScrollContainer VerticalExpand="True"
HorizontalExpand="True"
Margin="2"
MinHeight="100"
MaxHeight="200"
HScrollEnabled="False">
<BoxContainer Name="EmotesList"
Orientation="Vertical"
HorizontalExpand="True"
Margin="2">
</BoxContainer>
</ScrollContainer>
</PanelContainer>
</controls:EmotesTabControl>

View file

@ -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<EmotePrototype>()
.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<BoxContainer>()
.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<EmotePrototype>(emote.ID));
return button;
}
private bool IsVerbsAvailable(EmotePrototype emote, EntityUid player)
{
var whitelistSystem = _entManager.System<EntityWhitelistSystem>();
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<SpeechComponent>(player, out var speech) &&
!speech.AllowedEmotes.Contains(emote.ID))
return false;
return true;
}
private void OnPlayVerb(ProtoId<EmotePrototype> protoId)
{
var currentTime = _gameTiming.CurTime;
if (currentTime - _lastVerbTime < VerbCooldown)
return;
_lastVerbTime = currentTime;
_entManager.RaisePredictiveEvent(new PlayEmoteMessage(protoId));
}
protected override void Resized()
{
UpdateButtonsLayout();
}
}

View file

@ -0,0 +1,20 @@
<controls:VerbsTabControl
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client._Sunrise.UserActions.Tabs">
<PanelContainer StyleClasses="BackgroundDark">
<ScrollContainer VerticalExpand="True"
HorizontalExpand="True"
Margin="2"
MinHeight="100"
MaxHeight="200"
HScrollEnabled="False">
<BoxContainer Name="VerbsList"
Orientation="Vertical"
HorizontalExpand="True"
Margin="2">
</BoxContainer>
</ScrollContainer>
</PanelContainer>
</controls:VerbsTabControl>

View file

@ -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<UserActionUISystem>
{
private UserActionsPanel? _panel;
private List<BaseTabControl> _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<BaseTabControl> GetTabs() => _tabs;
}

View file

@ -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<LocalPlayerAttachedEvent>(LocalPlayerAttached);
SubscribeLocalEvent<LocalPlayerDetachedEvent>(LocalPlayerDetached);
}
private void LocalPlayerAttached(LocalPlayerAttachedEvent ev)
=> PlayerAttachedEvent?.Invoke();
private void LocalPlayerDetached(LocalPlayerDetachedEvent ev)
=> PlayerDetachedEvent?.Invoke();
}

View file

@ -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<UserActionUIController>();
_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;
}
}

View file

@ -0,0 +1,27 @@
<controls:UserActionsPanel
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client._Sunrise.UserActions"
xmlns:tabs="clr-namespace:Content.Client._Sunrise.UserActions.Tabs">
<PanelContainer StyleClasses="BackgroundDark" Name="MainPanel" MinWidth="300" MinHeight="200" HorizontalExpand="True">
<BoxContainer Orientation="Vertical" Name="BoxContainer" Margin="4" VerticalExpand="True">
<TabContainer Name="TabContainer" HorizontalExpand="True" VerticalExpand="True">
<tabs:EmotesTabControl Name="EmotesTabControl"/>
<tabs:VerbsTabControl Name="VerbTabControl"/>
</TabContainer>
<Label Name="NoContentLabel"
Text="{Loc 'user-action-control-nothing'}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
StyleClasses="LabelBig"
Margin="0 8"
Visible="False"/>
</BoxContainer>
</PanelContainer>
</controls:UserActionsPanel>

View file

@ -59,7 +59,7 @@ namespace Content.IntegrationTests.Tests.Buckle
var entityManager = server.ResolveDependency<IEntityManager>();
var actionBlocker = entityManager.System<ActionBlockerSystem>();
var buckleSystem = entityManager.System<SharedBuckleSystem>();
var standingState = entityManager.System<StandingStateSystem>();
var standingState = entityManager.System<SharedStandingStateSystem>();
var xformSystem = entityManager.System<SharedTransformSystem>();
EntityUid human = default;

View file

@ -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!;

View file

@ -202,6 +202,7 @@ public sealed partial class NPCCombatSystem
return;
}
_gun.SetTarget(gun, comp.Target);
_gun.AttemptShoot(uid, gunUid, gun, targetCordinates);
}
}

View file

@ -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);

View file

@ -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<ChangeLayingDownEvent>(OnChangeState);
SubscribeLocalEvent<StandingStateComponent, DropHandItemsEvent>(FallOver);
}
private void OnChangeState(ChangeLayingDownEvent msg, EntitySessionEventArgs args)
{
if (args.SenderSession is not { AttachedEntity: { Valid: true } uid }
|| !Exists(uid)
|| !TryComp<StandingStateComponent>(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<TransformComponent>(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<StandingStateComponent, DropHandItemsEvent>(FallOver);
}
}
/// <summary>
/// Raised after an entity falls down.
/// </summary>
public sealed class FellDownEvent : EntityEventArgs
{
public EntityUid Uid { get; }
public FellDownEvent(EntityUid uid)
{
Uid = uid;
}
}

View file

@ -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<EmoteAnimationComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<EmoteAnimationComponent, EmoteEvent>(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);
}
}

View file

@ -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!;

View file

@ -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

View file

@ -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!;
/// <inheritdoc/>
public override void Initialize()

View file

@ -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()
{

View file

@ -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!;

View file

@ -81,5 +81,6 @@ public enum EmoteCategory : byte
Invalid = 0,
Vocal = 1 << 0,
Hands = 1 << 1,
Verb = 1 << 2,
General = byte.MaxValue
}

View file

@ -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";

View file

@ -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!;

View file

@ -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<CollisionWakeComponent>(target);
_standing.Down(target);
_appearance.SetData(target, MobStateVisuals.State, MobState.Dead);
_standing.Down(target);
break;
case MobState.Invalid:
//unused;

View file

@ -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!;

View file

@ -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()
{

View file

@ -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<StandingStateComponent, StandUpDoAfterEvent>(OnStandUpDoAfter);
SubscribeLocalEvent<StandingStateComponent, DownDoAfterEvent>(OnDownDoAfter);
SubscribeLocalEvent<StandingStateComponent, MoveEvent>(OnMove);
SubscribeLocalEvent<StandingStateComponent, RefreshMovementSpeedModifiersEvent>(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<StandingStateComponent> 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<HandsComponent>(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<KnockedDownComponent>(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<PhysicsComponent>(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<BuckleComponent>(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<BuckleComponent>(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<bool>(uid, BuckleVisuals.Buckled, out var buckled, appearance);
if (!buckled && (!_appearance.TryGetData<MobState>(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
}

View file

@ -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;
/// <summary>
/// List of fixtures that had their collision mask changed when the entity was downed.
/// Required for re-adding the collision mask.
/// </summary>
[DataField, AutoNetworkedField]
public List<string> ChangedFixtures = new();
}
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadOnly)]
public List<string> 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
}

View file

@ -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;

View file

@ -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
{
}
/// <summary>
/// Subscribe if you can potentially block a down attempt.
/// </summary>
public sealed class DownAttemptEvent : CancellableEntityEventArgs
{
}
/// <summary>
/// Subscribe if you can potentially block a stand attempt.
/// </summary>
public sealed class StandAttemptEvent : CancellableEntityEventArgs
{
}
/// <summary>
/// Raised when an entity becomes standing
/// </summary>
public sealed class StoodEvent : EntityEventArgs
{
}
/// <summary>
/// Raised when an entity is not standing
/// </summary>
public sealed class DownedEvent : EntityEventArgs
{
}
}

View file

@ -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!;
/// <summary>

View file

@ -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()

View file

@ -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;
}
/// <summary>
/// Attempts to shoot at the target coordinates. Resets the shot counter after every shot.
/// </summary>

View file

@ -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!;
};

View file

@ -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!;

View file

@ -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

View file

@ -0,0 +1,3 @@
user-action-control-nothing = Пока нечего отображать.
user-action-control-tab-emote = Эмоции
user-action-control-tab-verbs = Действия

View file

@ -249,6 +249,7 @@
- type: Carriable
- type: CanEscapeInventory
- type: Mood
- type: EmoteAnimation
# Sunrise-End
- type: Barotrauma
damage:

View file

@ -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

View file

@ -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

View file

@ -587,3 +587,6 @@ binds:
- function: CockGun # Sunrise-Edit
type: State
key: C
- function: ToggleStanding
type: State
key: R