diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1129f37c963..1a0160146b5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,17 +3,17 @@ # Sorting by path instead of by who added it one day :( # this isn't how codeowners rules work pls read the first comment instead of trying to force a sorting order -/Resources/ConfigPresets/WizardsDen/ @nikthechampiongr -/Content.*/Administration/ @DrSmugleaf @nikthechampiongr -/Resources/ServerInfo/ @nikthechampiongr -/Resources/ServerInfo/Guidebook/ServerRules/ @nikthechampiongr +/Resources/ConfigPresets/WizardsDen/ @nikthechampiongr @crazybrain23 +/Content.*/Administration/ @DrSmugleaf @nikthechampiongr @crazybrain23 +/Resources/ServerInfo/ @nikthechampiongr @crazybrain23 +/Resources/ServerInfo/Guidebook/ServerRules/ @nikthechampiongr @crazybrain23 /Resources/Prototypes/Maps/** @Emisse /Resources/Prototypes/Body/ @DrSmugleaf # suffering /Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf /Resources/Prototypes/Entities/Mobs/Species/ @DrSmugleaf -/Resources/Prototypes/Guidebook/rules.yml @nikthechampiongr +/Resources/Prototypes/Guidebook/rules.yml @nikthechampiongr @crazybrain23 /Content.*/Body/ @DrSmugleaf /Content.YAMLLinter @DrSmugleaf /Content.Shared/Damage/ @DrSmugleaf @@ -25,7 +25,7 @@ # SKREEEE /Content.*.Database/ @PJB3005 @DrSmugleaf -/Content.Shared.Database/Log*.cs @PJB3005 @DrSmugleaf @nikthechampiongr +/Content.Shared.Database/Log*.cs @PJB3005 @DrSmugleaf @nikthechampiongr @crazybrain23 /Pow3r/ @PJB3005 /Content.Server/Power/Pow3r/ @PJB3005 diff --git a/Content.Client/Buckle/BuckleSystem.cs b/Content.Client/Buckle/BuckleSystem.cs index 035e1300ca5..2a805346b59 100644 --- a/Content.Client/Buckle/BuckleSystem.cs +++ b/Content.Client/Buckle/BuckleSystem.cs @@ -65,7 +65,6 @@ private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref Ap !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 f3b7837f21a..8a1e6140185 100644 --- a/Content.Client/Chat/UI/EmotesMenu.xaml.cs +++ b/Content.Client/Chat/UI/EmotesMenu.xaml.cs @@ -35,7 +35,7 @@ public EmotesMenu() 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/Humanoid/HumanoidAppearanceSystem.cs b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs index 6eb5dd9ec98..262bedc5237 100644 --- a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs +++ b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs @@ -264,7 +264,8 @@ private void RemoveMarking(Marking marking, SpriteComponent spriteComp) spriteComp.RemoveLayer(index); } } - private void ApplyMarking(MarkingPrototype markingPrototype, + // Sunrsie-Edit + public void ApplyMarking(MarkingPrototype markingPrototype, IReadOnlyList? colors, bool visible, HumanoidAppearanceComponent humanoid, diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 6abdaeea2d1..8fb46612d25 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -58,7 +58,6 @@ public static void SetupContexts(IInputContextContainer contexts) human.AddFunction(ContentKeyFunctions.SwapHands); human.AddFunction(ContentKeyFunctions.Drop); human.AddFunction(ContentKeyFunctions.UseItemInHand); - human.AddFunction(ContentKeyFunctions.CockGun); human.AddFunction(ContentKeyFunctions.AltUseItemInHand); human.AddFunction(ContentKeyFunctions.OpenCharacterMenu); human.AddFunction(ContentKeyFunctions.OpenEmotesMenu); @@ -86,6 +85,13 @@ public static void SetupContexts(IInputContextContainer contexts) human.AddFunction(ContentKeyFunctions.Arcade2); human.AddFunction(ContentKeyFunctions.Arcade3); + // Sunrise-Start + human.AddFunction(ContentKeyFunctions.ToggleStanding); + human.AddFunction(ContentKeyFunctions.CockGun); + human.AddFunction(ContentKeyFunctions.Jump); + human.AddFunction(ContentKeyFunctions.Reloading); + // Sunrise-End + // actions should be common (for ghosts, mobs, etc) common.AddFunction(ContentKeyFunctions.OpenActionsMenu); diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutContainer.xaml.cs b/Content.Client/Lobby/UI/Loadouts/LoadoutContainer.xaml.cs index 36f0772d784..2ab40fb37d4 100644 --- a/Content.Client/Lobby/UI/Loadouts/LoadoutContainer.xaml.cs +++ b/Content.Client/Lobby/UI/Loadouts/LoadoutContainer.xaml.cs @@ -36,17 +36,18 @@ public LoadoutContainer(ProtoId proto, bool disabled, Formatte if (_protoManager.TryIndex(proto, out var loadProto)) { - var ent = _entManager.System().GetFirstOrNull(loadProto); + var ent = loadProto.DummyEntity ?? _entManager.System().GetFirstOrNull(loadProto); - if (ent != null) - { - _entity = _entManager.SpawnEntity(ent, MapCoordinates.Nullspace); - Sprite.SetEntity(_entity); + if (ent == null) + return; - var spriteTooltip = new Tooltip(); - spriteTooltip.SetMessage(FormattedMessage.FromUnformatted(_entManager.GetComponent(_entity.Value).EntityDescription)); - TooltipSupplier = _ => spriteTooltip; - } + _entity = _entManager.SpawnEntity(ent, MapCoordinates.Nullspace); + Sprite.SetEntity(_entity); + + var spriteTooltip = new Tooltip(); + spriteTooltip.SetMessage(FormattedMessage.FromUnformatted(_entManager.GetComponent(_entity.Value).EntityDescription)); + + TooltipSupplier = _ => spriteTooltip; } } diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml.cs b/Content.Client/Lobby/UI/LobbyGui.xaml.cs index c090b356c73..229fe6f4403 100644 --- a/Content.Client/Lobby/UI/LobbyGui.xaml.cs +++ b/Content.Client/Lobby/UI/LobbyGui.xaml.cs @@ -132,10 +132,12 @@ public LobbyGui() _configurationManager.OnValueChanged(SunriseCCVars.LobbyOpacity, OnLobbyOpacityChanged); _configurationManager.OnValueChanged(SunriseCCVars.ServersHubEnable, OnServersHubEnableChanged); + _configurationManager.OnValueChanged(SunriseCCVars.ServiceAuthEnabled, OnServiceAuthEnableChanged); _configurationManager.OnValueChanged(SunriseCCVars.ServerName, OnServerNameChanged, true); SetLobbyOpacity(_configurationManager.GetCVar(SunriseCCVars.LobbyOpacity)); SetServersHubEnable(_configurationManager.GetCVar(SunriseCCVars.ServersHubEnable)); + SetUserProfileEnable(_configurationManager.GetCVar(SunriseCCVars.ServiceAuthEnabled)); Chat.SetChatOpacity(); @@ -180,10 +182,20 @@ private void OnServersHubEnableChanged(bool enable) SetServersHubEnable(enable); } + private void OnServiceAuthEnableChanged(bool enable) + { + SetUserProfileEnable(enable); + } + private void SetServersHubEnable(bool enable) { ServersHubBox.Visible = enable; } + + private void SetUserProfileEnable(bool enable) + { + UserProfileBox.Visible = enable; + } // Sunrise-End private void OnLobbyOpacityChanged(float opacity) diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index 88df46aaea3..c7792fda35c 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -177,7 +177,6 @@ void AddCheckBox(string checkBoxName, bool currentState, Action(); + + 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/Tips/TippyUIController.cs b/Content.Client/Tips/TippyUIController.cs index 7737a3d6982..77c10193a5d 100644 --- a/Content.Client/Tips/TippyUIController.cs +++ b/Content.Client/Tips/TippyUIController.cs @@ -104,7 +104,7 @@ private Vector2 UpdatePosition(TippyUI tippy, Vector2 screenSize, FrameEventArgs ? -WaddleRotation : WaddleRotation; - if (EntityManager.TryGetComponent(_entity, out FootstepModifierComponent? step)) + if (EntityManager.TryGetComponent(_entity, out FootstepModifierComponent? step) && step.FootstepSoundCollection != null) { var audioParams = step.FootstepSoundCollection.Params .AddVolume(-7f) diff --git a/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml b/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml index 653302fae4c..65c75ac78fc 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 7b86859a1a2..ab946f524aa 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 dc8972970ac..79f0ce75c00 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(500), + 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.3f), 0.125f), + new AnimationTrackProperty.KeyFrame(new Vector2(0, 0.7f), 0.125f), + new AnimationTrackProperty.KeyFrame(new Vector2(0, 0.3f), 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/GhostTheme/GhostThemeBoundUserInterface.cs b/Content.Client/_Sunrise/GhostTheme/GhostThemeBoundUserInterface.cs new file mode 100644 index 00000000000..954bc600a0c --- /dev/null +++ b/Content.Client/_Sunrise/GhostTheme/GhostThemeBoundUserInterface.cs @@ -0,0 +1,51 @@ +// © SUNRISE, An EULA/CLA with a hosting restriction, full text: https://github.com/space-sunrise/space-station-14/blob/master/CLA.txt +using Content.Shared._Sunrise.GhostTheme; +using JetBrains.Annotations; + +namespace Content.Client._Sunrise.GhostTheme; + +[UsedImplicitly] +public sealed class GhostThemeBoundUserInterface : BoundUserInterface +{ + [ViewVariables] + private GhostThemeMenu? _menu; + + public GhostThemeBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _menu = new GhostThemeMenu(); + _menu.OnClose += Close; + _menu.OnIdSelected += OnIdSelected; + _menu.OpenCentered(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + if (state is not GhostThemeBoundUserInterfaceState st) + return; + + _menu?.UpdateState(st.GhostThemes); + } + + private void OnIdSelected(string selectedId) + { + SendMessage(new GhostThemePrototypeSelectedMessage(selectedId)); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _menu?.Close(); + _menu = null; + } + } +} diff --git a/Content.Client/_Sunrise/GhostTheme/GhostThemeMenu.xaml b/Content.Client/_Sunrise/GhostTheme/GhostThemeMenu.xaml new file mode 100644 index 00000000000..651d18a81cc --- /dev/null +++ b/Content.Client/_Sunrise/GhostTheme/GhostThemeMenu.xaml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/Content.Client/_Sunrise/GhostTheme/GhostThemeMenu.xaml.cs b/Content.Client/_Sunrise/GhostTheme/GhostThemeMenu.xaml.cs new file mode 100644 index 00000000000..958d0212cee --- /dev/null +++ b/Content.Client/_Sunrise/GhostTheme/GhostThemeMenu.xaml.cs @@ -0,0 +1,76 @@ +// © SUNRISE, An EULA/CLA with a hosting restriction, full text: https://github.com/space-sunrise/space-station-14/blob/master/CLA.txt +using System.Numerics; +using Content.Client.Stylesheets; +using Content.Shared._Sunrise.GhostTheme; +using Content.Shared._Sunrise.SunriseCCVars; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Client.Utility; +using Robust.Shared.Configuration; +using Robust.Shared.Prototypes; + +namespace Content.Client._Sunrise.GhostTheme; + +[GenerateTypedNameReferences] +public sealed partial class GhostThemeMenu : DefaultWindow +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + + public event Action? OnIdSelected; + + private List _availableGhostThemes = []; + + public GhostThemeMenu() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + } + + public void UpdateState(List ghostThemes) + { + _availableGhostThemes = ghostThemes; + UpdateGrid(); + } + + private void UpdateGrid() + { + ClearGrid(); + + foreach (var ghostTheme in _availableGhostThemes) + { + if (!_prototypeManager.TryIndex(ghostTheme, out GhostThemePrototype? ghostThemePrototype)) + continue; + + var button = new Button + { + SetSize = new Vector2(128, 128), + HorizontalExpand = true, + ToggleMode = false, + StyleClasses = {StyleBase.ButtonSquare}, + }; + button.OnPressed += _ => + { + OnIdSelected?.Invoke(ghostTheme); + _cfg.SetCVar(SunriseCCVars.SponsorGhostTheme, ghostTheme); + _cfg.SaveToFile(); + }; + Grid.AddChild(button); + + var ghost = new TextureRect() + { + Texture = ghostThemePrototype.Sprite.Frame0(), + Stretch = TextureRect.StretchMode.KeepAspectCentered, + }; + + button.AddChild(ghost); + } + } + + private void ClearGrid() + { + Grid.RemoveAllChildren(); + } +} diff --git a/Content.Client/_Sunrise/GhostTheme/GhostThemeSystem.cs b/Content.Client/_Sunrise/GhostTheme/GhostThemeSystem.cs index 0bc9a59d4d4..d3eca172f39 100644 --- a/Content.Client/_Sunrise/GhostTheme/GhostThemeSystem.cs +++ b/Content.Client/_Sunrise/GhostTheme/GhostThemeSystem.cs @@ -1,4 +1,6 @@ +// © SUNRISE, An EULA/CLA with a hosting restriction, full text: https://github.com/space-sunrise/space-station-14/blob/master/CLA.txt using Content.Shared._Sunrise.GhostTheme; +using Content.Shared.Weapons.Ranged.Systems; using Robust.Client.GameObjects; using Robust.Shared.Prototypes; @@ -7,6 +9,7 @@ namespace Content.Client._Sunrise.GhostTheme; public sealed class GhostThemeSystem: EntitySystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + public override void Initialize() { base.Initialize(); @@ -17,16 +20,21 @@ private void OnInit(EntityUid uid, GhostThemeComponent component, ref AfterAutoH { if (component.GhostTheme == null || !_prototypeManager.TryIndex(component.GhostTheme, out var ghostThemePrototype)) - { return; - } - foreach (var entry in ghostThemePrototype.Components.Values) + + if (!EntityManager.TryGetComponent(uid, out var sprite)) + return; + + if (!sprite.LayerMapTryGet(EffectLayers.Unshaded, out var layer)) { - if (entry.Component is not SpriteComponent spriteComponent || - !EntityManager.TryGetComponent(uid, out var targetsprite)) - continue; - targetsprite.CopyFrom(spriteComponent); - targetsprite.LayerSetShader(0, "unshaded"); + sprite.LayerSetSprite(layer, ghostThemePrototype.Sprite); + sprite.LayerSetShader(layer, "unshaded"); + sprite.LayerSetColor(layer, ghostThemePrototype.SpriteColor); + sprite.LayerSetScale(layer, ghostThemePrototype.Scale); } + + sprite.DrawDepth = DrawDepth.Default + 11; + sprite.OverrideContainerOcclusion = true; + sprite.NoRotation = true; } } diff --git a/Content.Client/_Sunrise/Jump/JumpSystem.cs b/Content.Client/_Sunrise/Jump/JumpSystem.cs new file mode 100644 index 00000000000..10a73ee3dd9 --- /dev/null +++ b/Content.Client/_Sunrise/Jump/JumpSystem.cs @@ -0,0 +1,41 @@ +using Content.Shared._Sunrise.Jump; +using Content.Shared.Chat; +using Content.Shared.Chat.Prototypes; +using Content.Shared.Input; +using Robust.Shared.Input.Binding; +using Robust.Shared.Player; +using Robust.Shared.Timing; + +namespace Content.Client._Sunrise.Jump; + +public sealed class JumpSystem : SharedJumpSystem +{ + [Dependency] private readonly IGameTiming _gameTiming = default!; + + private TimeSpan _lastJumpTime; + private static readonly TimeSpan JumpCooldown = TimeSpan.FromSeconds(1); + + [ValidatePrototypeId] + private const string EmoteJumpProto = "EmoteJump"; + + public override void Initialize() + { + CommandBinds.Builder + .Bind(ContentKeyFunctions.Jump, InputCmdHandler.FromDelegate(Jump, handle: false, outsidePrediction: false)) + .Register(); + } + + private void Jump(ICommonSession? session) + { + if (session is not { AttachedEntity: { Valid: true } uid } _ + || !Exists(uid)) + return; + + var currentTime = _gameTiming.CurTime; + if (currentTime - _lastJumpTime < JumpCooldown) + return; + + _lastJumpTime = currentTime; + RaisePredictiveEvent(new PlayEmoteMessage(EmoteJumpProto)); + } +} diff --git a/Content.Client/_Sunrise/Roadmap/RoadmapUIController.cs b/Content.Client/_Sunrise/Roadmap/RoadmapUIController.cs index 00da29091cc..c142fbb44ea 100644 --- a/Content.Client/_Sunrise/Roadmap/RoadmapUIController.cs +++ b/Content.Client/_Sunrise/Roadmap/RoadmapUIController.cs @@ -13,7 +13,7 @@ public void OnStateEntered(LobbyState state) if (_shown || _window != null) return; - ToggleRoadmap(); + //ToggleRoadmap(); } public void ToggleRoadmap() diff --git a/Content.Client/_Sunrise/SponsorTiers/SponsorTierEntry.xaml b/Content.Client/_Sunrise/SponsorTiers/SponsorTierEntry.xaml new file mode 100644 index 00000000000..f7e851e5c8b --- /dev/null +++ b/Content.Client/_Sunrise/SponsorTiers/SponsorTierEntry.xaml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/_Sunrise/SponsorTiers/SponsorTierEntry.xaml.cs b/Content.Client/_Sunrise/SponsorTiers/SponsorTierEntry.xaml.cs new file mode 100644 index 00000000000..256199cbe8b --- /dev/null +++ b/Content.Client/_Sunrise/SponsorTiers/SponsorTierEntry.xaml.cs @@ -0,0 +1,577 @@ +// © SUNRISE, An EULA/CLA with a hosting restriction, full text: https://github.com/space-sunrise/space-station-14/blob/master/CLA.txt +using System.Numerics; +using Content.Client._Sunrise.TTS; +using Content.Client.Humanoid; +using Content.Client.Lobby; +using Content.Client.Resources; +using Content.Client.Stylesheets; +using Content.Shared._Sunrise.GhostTheme; +using Content.Shared._Sunrise.TTS; +using Content.Shared.Clothing; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Markings; +using Content.Shared.Humanoid.Prototypes; +using Content.Shared.Preferences; +using Content.Shared.Preferences.Loadouts; +using Content.Shared.Roles; +using Content.Sunrise.Interfaces.Shared; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Client.Utility; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Client._Sunrise.SponsorTiers; + +[GenerateTypedNameReferences] +public sealed partial class SponsorTierEntry : Control +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + + private readonly LobbyUIController _lobbyUIController; + private readonly ISharedSponsorsManager? _sponsorsManager; + + private float _accumulatedTime; + private readonly List _spriteViews = new(); + private readonly SponsorInfo _sponsorInfoTier; + + public int Index { get; } + + public SponsorTierEntry(SponsorInfo sponsorTier, int index) + { + IoCManager.InjectDependencies(this); + RobustXamlLoader.Load(this); + IoCManager.Instance!.TryResolveType(out _sponsorsManager); + _lobbyUIController = UserInterfaceManager.GetUIController(); + + Index = index; + _sponsorInfoTier = sponsorTier; + + LoadTierInfo(sponsorTier.Tier, sponsorTier.OOCColor, sponsorTier.ExtraSlots, sponsorTier.HavePriorityJoin, sponsorTier.AllowedRespawn); + LoadOpenAntags(sponsorTier.OpenAntags); + LoadPriorityAntags(sponsorTier.PriorityAntags); + LoadAllowedMarkings(sponsorTier.AllowedMarkings); + LoadAllowedSpecies(sponsorTier.AllowedSpecies); + LoadGhostThemes(sponsorTier.GhostThemes); + LoadPriorityGhostRoles(sponsorTier.PriorityGhostRoles); + LoadOpenGhostRoles(sponsorTier.OpenGhostRoles); + LoadAllowedVoices(sponsorTier.AllowedVoices); + LoadAllowedLoadouts(sponsorTier.AllowedLoadouts); + if (_preferencesManager.ServerDataLoaded) + { + LoadOpenRoles(_sponsorInfoTier.OpenRoles); + LoadPriorityRoles(_sponsorInfoTier.PriorityRoles); + LoadBypassRoles(_sponsorInfoTier.BypassRoles); + } + else + { + _preferencesManager.OnServerDataLoaded += PreferencesDataLoaded; + } + } + + private void PreferencesDataLoaded() + { + LoadOpenRoles(_sponsorInfoTier.OpenRoles); + LoadPriorityRoles(_sponsorInfoTier.PriorityRoles); + LoadBypassRoles(_sponsorInfoTier.BypassRoles); + } + + private void LoadTierInfo(int tier, string? oocColor, int extraSlots, bool priorityJoin, bool allowedRespawn) + { + TierLabel.Text = $"{tier}"; + if (!string.IsNullOrEmpty(oocColor)) + { + OocColorLabel.Text = "цвет"; + OocColorLabel.FontColorOverride = Color.FromHex(oocColor); + } + ExtraSlotsLabel.Text = $"{extraSlots}"; + PriorityJoinLabel.Text = priorityJoin ? "Да" : "Нет"; + AllowedRespawnLabel.Text = allowedRespawn ? "Да" : "Нет"; + } + + private void LoadOpenGhostRoles(IReadOnlyCollection openGhostRoles) + { + foreach (var openGhostRole in openGhostRoles) + { + if (!_prototypeManager.TryIndex(openGhostRole, out EntityPrototype? ghostRolePrototype)) + continue; + + var dummyEnt = _entityManager.SpawnEntity(ghostRolePrototype.ID, MapCoordinates.Nullspace); + + var view = new SpriteView + { + SetSize = new Vector2(128, 128), + Scale = new Vector2(4, 4) + }; + + view.SetEntity(dummyEnt); + _spriteViews.Add(view); + + var panel = CreateEntityIcon($"ent-{ghostRolePrototype.ID}", view); + + OpenGhostRolesGrid.AddChild(panel); + } + + OpenGhostRolesBox.Visible = OpenGhostRolesGrid.ChildCount > 0; + } + + private void LoadPriorityGhostRoles(IReadOnlyCollection priorityGhostRoles) + { + foreach (var priorityGhostRole in priorityGhostRoles) + { + if (!_prototypeManager.TryIndex(priorityGhostRole, out EntityPrototype? ghostRolePrototype)) + continue; + + var dummyEnt = _entityManager.SpawnEntity(ghostRolePrototype.ID, MapCoordinates.Nullspace); + + var view = new SpriteView + { + SetSize = new Vector2(128, 128), + Scale = new Vector2(4, 4) + }; + + view.SetEntity(dummyEnt); + _spriteViews.Add(view); + + var panel = CreateEntityIcon($"ent-{ghostRolePrototype.ID}", view); + + PriorityGhostRolesGrid.AddChild(panel); + } + + PriorityGhostRolesBox.Visible = PriorityGhostRolesGrid.ChildCount > 0; + } + + private void LoadAllowedVoices(IReadOnlyCollection allowedVoices) + { + foreach (var allowedVoice in allowedVoices) + { + if (!_prototypeManager.TryIndex(allowedVoice, out TTSVoicePrototype? voicePrototype)) + continue; + + var button = new Button() + { + HorizontalAlignment = HAlignment.Center, + Text = Loc.GetString(voicePrototype.Name), + StyleClasses = { "LabelKeyText" }, + SetSize = new Vector2(315,30), + }; + + button.OnPressed += _ => + { + _entityManager.System().RequestPreviewTts(allowedVoice); + }; + + TTSVoicesGrid.AddChild(button); + } + + TTSVoicesBox.Visible = TTSVoicesGrid.ChildCount > 0; + } + + private void LoadAllowedLoadouts(IReadOnlyCollection allowedLoadouts) + { + foreach (var allowedLoadout in allowedLoadouts) + { + if (!_prototypeManager.TryIndex(allowedLoadout, out LoadoutPrototype? loadoutPrototype)) + continue; + + var entProtoId = _entityManager.System().GetFirstOrNull(loadoutPrototype); + var name = _entityManager.System().GetName(loadoutPrototype); + + var view = new SpriteView + { + SetSize = new Vector2(128, 128), + Scale = new Vector2(6, 6) + }; + _spriteViews.Add(view); + + if (entProtoId != null) + { + var dummyEnt = _entityManager.SpawnEntity(entProtoId, MapCoordinates.Nullspace); + view.SetEntity(dummyEnt); + } + + var panel = CreateEntityIcon(name, view); + + AllowedLoadoutsGrid.AddChild(panel); + } + + AllowedLoadoutsBox.Visible = AllowedLoadoutsGrid.ChildCount > 0; + } + + private void LoadGhostThemes(IReadOnlyCollection ghostThemes) + { + foreach (var ghostTheme in ghostThemes) + { + if (!_prototypeManager.TryIndex(ghostTheme, out GhostThemePrototype? ghostThemePrototype)) + continue; + + var panel = CreateIcon(ghostThemePrototype.Name, ghostThemePrototype.Sprite); + + GhostThemesGrid.AddChild(panel); + } + + GhostThemesBox.Visible = GhostThemesGrid.ChildCount > 0; + } + + private void LoadBypassRoles(IReadOnlyCollection bypassRoles) + { + foreach (var bypassRole in bypassRoles) + { + if (!_prototypeManager.TryIndex(bypassRole, out JobPrototype? roleProto)) + continue; + + var sponsorPrototypes = _sponsorsManager?.GetClientPrototypes().ToArray() ?? []; + + var humanoid = (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter; + if (humanoid == null) + continue; + + var previewEntity = roleProto.JobPreviewEntity ?? (EntProtoId?)roleProto.JobEntity; + + EntityUid dummyEnt; + + if (previewEntity == null) + { + var dummy = _prototypeManager.Index(humanoid.Species).DollPrototype; + dummyEnt = _entityManager.SpawnEntity(dummy, MapCoordinates.Nullspace); + + _entityManager.System().LoadProfile(dummyEnt, humanoid); + _lobbyUIController.GiveDummyJobClothes(dummyEnt, humanoid, roleProto); + + if (_prototypeManager.HasIndex(LoadoutSystem.GetJobPrototype(roleProto.ID))) + { + var loadout = humanoid.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(roleProto.ID), _playerManager.LocalSession, humanoid.Species, _entityManager, _prototypeManager, sponsorPrototypes); + _lobbyUIController.GiveDummyLoadout(dummyEnt, loadout, true); + } + } + else + { + dummyEnt = _entityManager.SpawnEntity(previewEntity, MapCoordinates.Nullspace); + } + + var view = new SpriteView + { + SetSize = new Vector2(128, 128), + Scale = new Vector2(4, 4) + }; + + view.SetEntity(dummyEnt); + _spriteViews.Add(view); + + var panel = CreateEntityIcon(roleProto.Name, view); + + BypassRolesGrid.AddChild(panel); + } + + BypassRolesBox.Visible = BypassRolesGrid.ChildCount > 0; + } + + private void LoadAllowedSpecies(IReadOnlyCollection speciesList) + { + foreach (var species in speciesList) + { + if (!_prototypeManager.TryIndex(species, out SpeciesPrototype? speciesPrototype)) + continue; + + var dummyEnt = _entityManager.SpawnEntity(speciesPrototype.DollPrototype, MapCoordinates.Nullspace); + + var view = new SpriteView + { + SetSize = new Vector2(128, 128), + Scale = new Vector2(4, 4), + }; + view.SetEntity(dummyEnt); + _spriteViews.Add(view); + + var panel = CreateEntityIcon(speciesPrototype.Name, view); + + AllowedSpeciesGrid.AddChild(panel); + } + + AllowedSpeciesBox.Visible = AllowedSpeciesGrid.ChildCount > 0; + } + + private void LoadAllowedMarkings(IReadOnlyCollection markingsList) + { + foreach (var marking in markingsList) + { + if (!_prototypeManager.TryIndex(marking, out MarkingPrototype? markingProto)) + continue; + + var species = markingProto.SpeciesRestrictions is { Count: > 0 } + ? markingProto.SpeciesRestrictions[0] + : SharedHumanoidAppearanceSystem.DefaultSpecies; + + if (!_prototypeManager.TryIndex(species, out SpeciesPrototype? speciesProto)) + continue; + + var dummyEnt = _entityManager.SpawnEntity(speciesProto.DollPrototype, MapCoordinates.Nullspace); + + var humanoidAppearance = _entityManager.EnsureComponent(dummyEnt); + var spriteComponent = _entityManager.EnsureComponent(dummyEnt); + + var view = new SpriteView + { + SetSize = new Vector2(128, 128), + Scale = new Vector2(4, 4), + }; + + _entityManager.System().ApplyMarking(markingProto, null, true, humanoidAppearance, spriteComponent); + + view.SetEntity(dummyEnt); + _spriteViews.Add(view); + + var panel = CreateEntityIcon($"marking-{markingProto.ID}", view); + + AllowedMarkingsGrid.AddChild(panel); + } + + AllowedMarkingsBox.Visible = AllowedMarkingsGrid.ChildCount > 0; + + } + + private void LoadPriorityAntags(IReadOnlyCollection priorityAntags) + { + foreach (var priorityAntag in priorityAntags) + { + if (!_prototypeManager.TryIndex(priorityAntag, out AntagPrototype? antagProto)) + continue; + + var panel = CreateIcon(antagProto.Name, antagProto.PreviewIcon); + + PriorityAntagGrid.AddChild(panel); + } + + PriorityAntagBox.Visible = PriorityAntagGrid.ChildCount > 0; + } + + private void LoadOpenAntags(IReadOnlyCollection openAntags) + { + foreach (var openAntag in openAntags) + { + if (!_prototypeManager.TryIndex(openAntag, out AntagPrototype? antagProto)) + continue; + + var panel = CreateIcon(antagProto.Name, antagProto.PreviewIcon); + + OpenAntagGrid.AddChild(panel); + } + + OpenAntagBox.Visible = OpenAntagGrid.ChildCount > 0; + } + + private void LoadPriorityRoles(IReadOnlyCollection priorityRoles) + { + foreach (var priorityRole in priorityRoles) + { + if (!_prototypeManager.TryIndex(priorityRole, out JobPrototype? roleProto)) + continue; + + var sponsorPrototypes = _sponsorsManager?.GetClientPrototypes().ToArray() ?? []; + + var humanoid = (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter; + if (humanoid == null) + continue; + + var previewEntity = roleProto.JobPreviewEntity ?? (EntProtoId?)roleProto.JobEntity; + + EntityUid dummyEnt; + + if (previewEntity == null) + { + var dummy = _prototypeManager.Index(humanoid.Species).DollPrototype; + dummyEnt = _entityManager.SpawnEntity(dummy, MapCoordinates.Nullspace); + + _entityManager.System().LoadProfile(dummyEnt, humanoid); + _lobbyUIController.GiveDummyJobClothes(dummyEnt, humanoid, roleProto); + + if (_prototypeManager.HasIndex(LoadoutSystem.GetJobPrototype(roleProto.ID))) + { + var loadout = humanoid.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(roleProto.ID), _playerManager.LocalSession, humanoid.Species, _entityManager, _prototypeManager, sponsorPrototypes); + _lobbyUIController.GiveDummyLoadout(dummyEnt, loadout, true); + } + } + else + { + dummyEnt = _entityManager.SpawnEntity(previewEntity, MapCoordinates.Nullspace); + } + + var view = new SpriteView + { + SetSize = new Vector2(128, 128), + Scale = new Vector2(4, 4) + }; + + view.SetEntity(dummyEnt); + _spriteViews.Add(view); + + var panel = CreateEntityIcon(roleProto.Name, view); + + PriorityRolesGrid.AddChild(panel); + } + + PriorityRolesBox.Visible = PriorityRolesGrid.ChildCount > 0; + } + + private void LoadOpenRoles(IReadOnlyCollection openRoles) + { + foreach (var openRole in openRoles) + { + if (!_prototypeManager.TryIndex(openRole, out JobPrototype? roleProto)) + continue; + + var sponsorPrototypes = _sponsorsManager?.GetClientPrototypes().ToArray() ?? []; + + var humanoid = (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter; + if (humanoid == null) + continue; + + var previewEntity = roleProto.JobPreviewEntity ?? (EntProtoId?)roleProto.JobEntity; + + EntityUid dummyEnt; + + if (previewEntity == null) + { + var dummy = _prototypeManager.Index(humanoid.Species).DollPrototype; + dummyEnt = _entityManager.SpawnEntity(dummy, MapCoordinates.Nullspace); + + _entityManager.System().LoadProfile(dummyEnt, humanoid); + _lobbyUIController.GiveDummyJobClothes(dummyEnt, humanoid, roleProto); + + if (_prototypeManager.HasIndex(LoadoutSystem.GetJobPrototype(roleProto.ID))) + { + var loadout = humanoid.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(roleProto.ID), _playerManager.LocalSession, humanoid.Species, _entityManager, _prototypeManager, sponsorPrototypes); + _lobbyUIController.GiveDummyLoadout(dummyEnt, loadout, true); + } + } + else + { + dummyEnt = _entityManager.SpawnEntity(previewEntity, MapCoordinates.Nullspace); + } + + var view = new SpriteView + { + SetSize = new Vector2(128, 128), + Scale = new Vector2(4, 4) + }; + + view.SetEntity(dummyEnt); + _spriteViews.Add(view); + + var panel = CreateEntityIcon(roleProto.Name, view); + + OpenRolesGrid.AddChild(panel); + } + + OpenRolesBox.Visible = OpenRolesGrid.ChildCount > 0; + } + + private PanelContainer CreateEntityIcon(string name, SpriteView spriteView) + { + var panelTex = _resourceCache.GetTexture("/Textures/Interface/Nano/light_panel_background_bordered.png"); + var back = new StyleBoxTexture + { + Texture = panelTex, + }; + back.SetPatchMargin(StyleBox.Margin.All, 10); + + var panel = new PanelContainer() + { + SetSize = new Vector2(200, 200), + Margin = new Thickness(5, 5, 5, 5), + StyleClasses = { StyleBase.ButtonSquare }, + }; + + var box = new BoxContainer() + { + Orientation = BoxContainer.LayoutOrientation.Vertical, + Margin = new Thickness(5, 5, 5, 5), + StyleClasses = { StyleBase.ButtonSquare }, + Align = BoxContainer.AlignMode.Center + }; + + panel.AddChild(box); + panel.PanelOverride = back; + + var title = new RichTextLabel() + { + HorizontalAlignment = HAlignment.Center, + Text = Loc.GetString(name), + StyleClasses = { "LabelKeyText" } + }; + + box.AddChild(spriteView); + box.AddChild(title); + + return panel; + } + + private PanelContainer CreateIcon(string name, SpriteSpecifier spriteSpecifier) + { + var panelTex = _resourceCache.GetTexture("/Textures/Interface/Nano/light_panel_background_bordered.png"); + var back = new StyleBoxTexture + { + Texture = panelTex, + }; + back.SetPatchMargin(StyleBox.Margin.All, 10); + + var panel = new PanelContainer() + { + SetSize = new Vector2(200, 200), + Margin = new Thickness(5, 5, 5, 5), + StyleClasses = { StyleBase.ButtonSquare }, + }; + + var box = new BoxContainer() + { + Orientation = BoxContainer.LayoutOrientation.Vertical, + Margin = new Thickness(5, 5, 5, 5), + StyleClasses = { StyleBase.ButtonSquare }, + }; + + panel.AddChild(box); + panel.PanelOverride = back; + + var icon = new TextureRect() + { + SetSize = new Vector2(128, 128), + Texture = spriteSpecifier.Frame0(), + Stretch = TextureRect.StretchMode.KeepAspectCentered, + }; + + var title = new RichTextLabel() + { + HorizontalAlignment = HAlignment.Center, + Text = Loc.GetString(name), + StyleClasses = { "LabelKeyText" } + }; + + box.AddChild(icon); + box.AddChild(title); + + return panel; + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + _accumulatedTime += args.DeltaSeconds; + foreach (var spriteView in _spriteViews) + { + spriteView.OverrideDirection = (Direction) ((int) _accumulatedTime % 4 * 2); + } + } +} diff --git a/Content.Client/_Sunrise/SponsorTiers/SponsorTiers.xaml b/Content.Client/_Sunrise/SponsorTiers/SponsorTiers.xaml new file mode 100644 index 00000000000..191942c6528 --- /dev/null +++ b/Content.Client/_Sunrise/SponsorTiers/SponsorTiers.xaml @@ -0,0 +1,7 @@ + + + + + diff --git a/Content.Client/_Sunrise/SponsorTiers/SponsorTiers.xaml.cs b/Content.Client/_Sunrise/SponsorTiers/SponsorTiers.xaml.cs new file mode 100644 index 00000000000..53a40e75d51 --- /dev/null +++ b/Content.Client/_Sunrise/SponsorTiers/SponsorTiers.xaml.cs @@ -0,0 +1,66 @@ +// © SUNRISE, An EULA/CLA with a hosting restriction, full text: https://github.com/space-sunrise/space-station-14/blob/master/CLA.txt +using System.Linq; +using Content.Client.Resources; +using Content.Client.Stylesheets; +using Content.Sunrise.Interfaces.Shared; +using Robust.Client.AutoGenerated; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using static Robust.Client.UserInterface.StylesheetHelpers; + +namespace Content.Client._Sunrise.SponsorTiers; + +[GenerateTypedNameReferences] +public sealed partial class SponsorTiers : Control +{ + [Dependency] private readonly IResourceCache _resourceCache = default!; + + private readonly ISharedSponsorsManager? _sponsorsManager; + + public SponsorTiers() + { + IoCManager.InjectDependencies(this); + RobustXamlLoader.Load(this); + + IoCManager.Instance!.TryResolveType(out _sponsorsManager); + if (_sponsorsManager == null) + return; + + _sponsorsManager.LoadedSponsorTiers += ReloadSponsorTiers; + + ReloadSponsorTiers(_sponsorsManager.GetSponsorTiers()); + } + + private void ReloadSponsorTiers(List sponsorTiers) + { + SponsorTiersContainer.RemoveAllChildren(); + for (var i = 0; i < sponsorTiers.Count; i++) + { + var sponsorTier = sponsorTiers[i]; + var entry = new SponsorTierEntry(sponsorTier, i); + SponsorTiersContainer.AddChild(entry); + SponsorTiersContainer.SetTabTitle(i, sponsorTier.Title ?? "No Title"); + var stylesheet = IoCManager.Resolve().SheetNano; + var notoSans20 = _resourceCache.GetFont( + [ + "/Fonts/NotoSans/NotoSans-Regular.ttf", + "/Fonts/NotoSans/NotoSansSymbols-Regular.ttf", + "/Fonts/NotoSans/NotoSansSymbols2-Regular.ttf", + ], + 20 + ); + var rules = stylesheet.Rules.ToList(); + var tabContainerFontRule = new StyleRule( + Element(), + new[] { new StyleProperty("font", notoSans20) } + ); + + rules.Add(tabContainerFontRule); + + stylesheet = new Stylesheet(rules.ToArray()); + SponsorTiersContainer.Stylesheet = stylesheet; + } + } +} diff --git a/Content.Client/_Sunrise/SponsorTiers/SponsorTiersUIController.cs b/Content.Client/_Sunrise/SponsorTiers/SponsorTiersUIController.cs new file mode 100644 index 00000000000..5d241d8e62f --- /dev/null +++ b/Content.Client/_Sunrise/SponsorTiers/SponsorTiersUIController.cs @@ -0,0 +1,56 @@ +// © SUNRISE, An EULA/CLA with a hosting restriction, full text: https://github.com/space-sunrise/space-station-14/blob/master/CLA.txt +using Content.Client.Lobby; +using Content.Sunrise.Interfaces.Shared; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controllers; + +namespace Content.Client._Sunrise.SponsorTiers; + +public partial class SponsorTiersUIController : UIController, IOnStateEntered +{ + [Dependency] private readonly IUserInterfaceManager _uiManager = default!; + + private SponsorTiersUi _sponsorTiersUi = default!; + private bool _shown; + + public void OnStateEntered(LobbyState state) + { + IoCManager.Instance!.TryResolveType(out var sponsors); + + if (_shown || sponsors == null || sponsors.ClientIsSponsor()) + return; + + ToggleWindow(); + _shown = true; + } + + public void OpenWindow() + { + EnsureWindow(); + + _sponsorTiersUi.OpenCentered(); + _sponsorTiersUi.MoveToFront(); + } + + private void EnsureWindow() + { + if (_sponsorTiersUi is { Disposed: false }) + return; + + _sponsorTiersUi = _uiManager.CreateWindow(); + } + + public void ToggleWindow() + { + EnsureWindow(); + + if (_sponsorTiersUi.IsOpen) + { + _sponsorTiersUi.Close(); + } + else + { + OpenWindow(); + } + } +} diff --git a/Content.Client/_Sunrise/SponsorTiers/SponsorTiersUi.xaml b/Content.Client/_Sunrise/SponsorTiers/SponsorTiersUi.xaml new file mode 100644 index 00000000000..69dcc1dc773 --- /dev/null +++ b/Content.Client/_Sunrise/SponsorTiers/SponsorTiersUi.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/Content.Client/_Sunrise/SponsorTiers/SponsorTiersUi.xaml.cs b/Content.Client/_Sunrise/SponsorTiers/SponsorTiersUi.xaml.cs new file mode 100644 index 00000000000..201249ebda4 --- /dev/null +++ b/Content.Client/_Sunrise/SponsorTiers/SponsorTiersUi.xaml.cs @@ -0,0 +1,27 @@ +// © SUNRISE, An EULA/CLA with a hosting restriction, full text: https://github.com/space-sunrise/space-station-14/blob/master/CLA.txt +using Content.Client._Sunrise.ServersHub; +using Content.Shared._Sunrise.ServersHub; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client._Sunrise.SponsorTiers; + +[GenerateTypedNameReferences] +public sealed partial class SponsorTiersUi : DefaultWindow +{ + [Dependency] private readonly ServersHubManager _serversHubManager = default!; + + public SponsorTiersUi() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + _serversHubManager.ServersDataListChanged += RefreshServersHubHeader; + } + + private void RefreshServersHubHeader(List servers) + { + + } +} diff --git a/Content.Client/_Sunrise/UserActions/Tabs/BaseTabControl.cs b/Content.Client/_Sunrise/UserActions/Tabs/BaseTabControl.cs new file mode 100644 index 00000000000..ecd8d9658ef --- /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 00000000000..2e5f35e0799 --- /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 00000000000..26a82c3fd12 --- /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 00000000000..8f2c55d99d2 --- /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 00000000000..214c4ffd45c --- /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 00000000000..e11ce45d443 --- /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 00000000000..a5eef992393 --- /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 00000000000..a2912e4bbfe --- /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 00000000000..2eae165c0be --- /dev/null +++ b/Content.Client/_Sunrise/UserActions/UserActionsPanel.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + diff --git a/Content.Client/_Sunrise/UserProfile/UserProfile.xaml b/Content.Client/_Sunrise/UserProfile/UserProfile.xaml index f077e1d4abb..863dc2b9796 100644 --- a/Content.Client/_Sunrise/UserProfile/UserProfile.xaml +++ b/Content.Client/_Sunrise/UserProfile/UserProfile.xaml @@ -20,7 +20,7 @@ HorizontalAlignment="Center" VerticalExpand="True" VerticalAlignment="Top"> -