diff --git a/Content.Client/Animations/AnimationsTestComponent.cs b/Content.Client/Animations/AnimationsTestComponent.cs deleted file mode 100644 index f951295ed5046d..00000000000000 --- a/Content.Client/Animations/AnimationsTestComponent.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using Robust.Client.Animations; -using Robust.Client.GameObjects; -using Robust.Shared.Animations; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Maths; - -namespace Content.Client.Animations -{ - [RegisterComponent] - public sealed class AnimationsTestComponent : Component - { - protected override void Initialize() - { - base.Initialize(); - - var animations = IoCManager.Resolve().GetComponent(Owner); - animations.Play(new Animation - { - Length = TimeSpan.FromSeconds(20), - AnimationTracks = - { - new AnimationTrackComponentProperty - { - ComponentType = typeof(TransformComponent), - Property = nameof(TransformComponent.LocalRotation), - InterpolationMode = AnimationInterpolationMode.Linear, - KeyFrames = - { - new AnimationTrackProperty.KeyFrame(Angle.Zero, 0), - new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(1440), 20) - } - }, - new AnimationTrackComponentProperty - { - ComponentType = typeof(SpriteComponent), - Property = "layer/0/texture", - KeyFrames = - { - new AnimationTrackProperty.KeyFrame("Objects/toolbox_r.png", 0), - new AnimationTrackProperty.KeyFrame("Objects/Toolbox_b.png", 5), - new AnimationTrackProperty.KeyFrame("Objects/Toolbox_y.png", 5), - new AnimationTrackProperty.KeyFrame("Objects/toolbox_r.png", 5), - } - } - } - }, "yes"); - } - } -} diff --git a/Content.Client/CartridgeLoader/Cartridges/NewsReadUi.cs b/Content.Client/CartridgeLoader/Cartridges/NewsReadUi.cs new file mode 100644 index 00000000000000..ce240e53a0d16b --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/NewsReadUi.cs @@ -0,0 +1,50 @@ +using Content.Client.UserInterface.Fragments; +using Content.Shared.CartridgeLoader.Cartridges; +using Content.Shared.CartridgeLoader; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface; + +namespace Content.Client.CartridgeLoader.Cartridges; + +public sealed class NewsReadUi : UIFragment +{ + private NewsReadUiFragment? _fragment; + + public override Control GetUIFragmentRoot() + { + return _fragment!; + } + + public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner) + { + _fragment = new NewsReadUiFragment(); + + _fragment.OnNextButtonPressed += () => + { + SendNewsReadMessage(NewsReadUiAction.Next, userInterface); + }; + _fragment.OnPrevButtonPressed += () => + { + SendNewsReadMessage(NewsReadUiAction.Prev, userInterface); + }; + _fragment.OnNotificationSwithPressed += () => + { + SendNewsReadMessage(NewsReadUiAction.NotificationSwith, userInterface); + }; + } + + public override void UpdateState(BoundUserInterfaceState state) + { + if (state is NewsReadBoundUserInterfaceState cast) + _fragment?.UpdateState(cast.Article, cast.TargetNum, cast.TotalNum, cast.NotificationOn); + else if (state is NewsReadEmptyBoundUserInterfaceState empty) + _fragment?.UpdateEmptyState(empty.NotificationOn); + } + + private void SendNewsReadMessage(NewsReadUiAction action, BoundUserInterface userInterface) + { + var newsMessage = new NewsReadUiMessageEvent(action); + var message = new CartridgeUiMessage(newsMessage); + userInterface.SendMessage(message); + } +} diff --git a/Content.Client/CartridgeLoader/Cartridges/NewsReadUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/NewsReadUiFragment.xaml new file mode 100644 index 00000000000000..7431713ea8e078 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/NewsReadUiFragment.xaml @@ -0,0 +1,54 @@ + + + + + + + diff --git a/Content.Client/MassMedia/Ui/MiniArticleCardControl.xaml.cs b/Content.Client/MassMedia/Ui/MiniArticleCardControl.xaml.cs new file mode 100644 index 00000000000000..86058959b46f6d --- /dev/null +++ b/Content.Client/MassMedia/Ui/MiniArticleCardControl.xaml.cs @@ -0,0 +1,26 @@ +using Content.Client.Message; +using Content.Shared.Research.Prototypes; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Client.MassMedia.Ui; + +[GenerateTypedNameReferences] +public sealed partial class MiniArticleCardControl : Control +{ + public Action? OnDeletePressed; + public int ArtcileNum; + + public MiniArticleCardControl(string name) + { + RobustXamlLoader.Load(this); + + Name.SetMarkup(name); + + Delete.OnPressed += _ => OnDeletePressed?.Invoke(); + } +} diff --git a/Content.Client/MassMedia/Ui/NewsWriteBoundUserInterface.cs b/Content.Client/MassMedia/Ui/NewsWriteBoundUserInterface.cs new file mode 100644 index 00000000000000..1ec19ad4119cab --- /dev/null +++ b/Content.Client/MassMedia/Ui/NewsWriteBoundUserInterface.cs @@ -0,0 +1,93 @@ +using Robust.Shared.Timing; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Content.Shared.MassMedia.Systems; +using Content.Shared.MassMedia.Components; +using Content.Client.GameTicking.Managers; +using Robust.Shared.Utility; + +namespace Content.Client.MassMedia.Ui +{ + [UsedImplicitly] + public sealed class NewsWriteBoundUserInterface : BoundUserInterface + { + [ViewVariables] + private NewsWriteMenu? _menu; + + [Dependency] private readonly IEntitySystemManager _entitySystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + private ClientGameTicker? _gameTicker; + + [ViewVariables] + private string _windowName = Loc.GetString("news-read-ui-default-title"); + + public NewsWriteBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + + } + + protected override void Open() + { + _menu = new NewsWriteMenu(_windowName); + + _menu.OpenCentered(); + _menu.OnClose += Close; + + _menu.ShareButtonPressed += OnShareButtonPressed; + _menu.DeleteButtonPressed += OnDeleteButtonPressed; + + _gameTicker = _entitySystem.GetEntitySystem(); + + SendMessage(new NewsWriteArticlesRequestMessage()); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + + _menu?.Close(); + _menu?.Dispose(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + if (_menu == null || state is not NewsWriteBoundUserInterfaceState cast) + return; + + _menu.UpdateUI(cast.Articles, cast.ShareAvalible); + } + + private void OnShareButtonPressed() + { + if (_menu == null || _menu.NameInput.Text.Length == 0) + return; + + var stringContent = Rope.Collapse(_menu.ContentInput.TextRope); + + if (stringContent == null || stringContent.Length == 0) return; + if (_gameTicker == null) return; + + NewsArticle article = new NewsArticle(); + var stringName = _menu.NameInput.Text; + var name = (stringName.Length <= 25 ? stringName.Trim() : $"{stringName.Trim().Substring(0, 25)}..."); + article.Name = name; + article.Content = stringContent; + article.ShareTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan); + + _menu.ContentInput.TextRope = new Rope.Leaf(string.Empty); + _menu.NameInput.Text = string.Empty; + + SendMessage(new NewsWriteShareMessage(article)); + } + + private void OnDeleteButtonPressed(int articleNum) + { + if (_menu == null) return; + + SendMessage(new NewsWriteDeleteMessage(articleNum)); + } + } +} diff --git a/Content.Client/MassMedia/Ui/NewsWriteMenu.xaml b/Content.Client/MassMedia/Ui/NewsWriteMenu.xaml new file mode 100644 index 00000000000000..08d113f8a95191 --- /dev/null +++ b/Content.Client/MassMedia/Ui/NewsWriteMenu.xaml @@ -0,0 +1,68 @@ + + + + + + + + + + + diff --git a/Content.Client/MassMedia/Ui/NewsWriteMenu.xaml.cs b/Content.Client/MassMedia/Ui/NewsWriteMenu.xaml.cs new file mode 100644 index 00000000000000..99b1f47fd1ed77 --- /dev/null +++ b/Content.Client/MassMedia/Ui/NewsWriteMenu.xaml.cs @@ -0,0 +1,44 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; +using Content.Shared.MassMedia.Systems; + +namespace Content.Client.MassMedia.Ui; + +[GenerateTypedNameReferences] +public sealed partial class NewsWriteMenu : DefaultWindow +{ + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + public event Action? ShareButtonPressed; + public event Action? DeleteButtonPressed; + + public NewsWriteMenu(string name) + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + if (Window != null) + Window.Title = name; + + Share.OnPressed += _ => ShareButtonPressed?.Invoke(); + } + + public void UpdateUI(NewsArticle[] articles, bool shareAvalible) + { + ArticleCardsContainer.Children.Clear(); + + for (int i = 0; i < articles.Length; i++) + { + var mini = new MiniArticleCardControl(articles[i].Name); + mini.ArtcileNum = i; + mini.OnDeletePressed += () => DeleteButtonPressed?.Invoke(mini.ArtcileNum); + + ArticleCardsContainer.AddChild(mini); + } + + Share.Disabled = !shareAvalible; + } +} diff --git a/Content.Client/PDA/PdaMenu.xaml b/Content.Client/PDA/PdaMenu.xaml index a06032d6d2b6ef..dbdcb4d90b27eb 100644 --- a/Content.Client/PDA/PdaMenu.xaml +++ b/Content.Client/PDA/PdaMenu.xaml @@ -1,4 +1,4 @@ -(OnAttachedEntityChanged); SubscribeLocalEvent(OnHandleState); SubscribeLocalEvent(OnGetStatusMessage); } @@ -27,8 +26,6 @@ private void OnHandleState(EntityUid uid, GeigerComponent component, ref Compone if (args.Current is not GeigerComponentState state) return; - UpdateGeigerSound(uid, state.IsEnabled, state.User, state.DangerLevel, false, component); - component.CurrentRadiation = state.CurrentRadiation; component.DangerLevel = state.DangerLevel; component.IsEnabled = state.IsEnabled; @@ -43,53 +40,4 @@ private void OnGetStatusMessage(EntityUid uid, GeigerComponent component, ItemSt args.Controls.Add(new GeigerItemControl(component)); } - - private void OnAttachedEntityChanged(PlayerAttachSysMessage ev) - { - // need to go for each component known to client - // and update their geiger sound - foreach (var geiger in EntityQuery()) - { - ForceUpdateGeigerSound(geiger.Owner, geiger); - } - } - - private void ForceUpdateGeigerSound(EntityUid uid, GeigerComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - UpdateGeigerSound(uid, component.IsEnabled, component.User, component.DangerLevel, true, component); - } - - private void UpdateGeigerSound(EntityUid uid, bool isEnabled, EntityUid? user, - GeigerDangerLevel dangerLevel, bool force = false, GeigerComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - // check if we even need to update sound - if (!force && isEnabled == component.IsEnabled && - user == component.User && dangerLevel == component.DangerLevel) - { - return; - } - - component.Stream?.Stop(); - - if (!isEnabled || user == null) - return; - if (!component.Sounds.TryGetValue(dangerLevel, out var sounds)) - return; - - // check that that local player controls entity that is holding geiger counter - if (_playerManager.LocalPlayer == null) - return; - var attachedEnt = _playerManager.LocalPlayer.Session.AttachedEntity; - if (attachedEnt != user) - return; - - var sound = _audio.GetSound(sounds); - var param = sounds.Params.WithLoop(true).WithVolume(-4f); - component.Stream = _audio.Play(sound, Filter.Local(), uid, false, param); - } } diff --git a/Content.Client/Replay/ContentReplayPlaybackManager.cs b/Content.Client/Replay/ContentReplayPlaybackManager.cs index 16cdfb79fecbdd..55491429ea6281 100644 --- a/Content.Client/Replay/ContentReplayPlaybackManager.cs +++ b/Content.Client/Replay/ContentReplayPlaybackManager.cs @@ -120,6 +120,9 @@ private bool OnHandleReplayMessage(object message, bool skipEffects) if (!_entMan.EntityExists(_player.LocalPlayer?.ControlledEntity)) _entMan.System().SetSpectatorPosition(default); return true; + case ChatMessage chat: + _uiMan.GetUIController().ProcessChatMessage(chat, speechBubble: !skipEffects); + return true; } if (!skipEffects) @@ -130,10 +133,6 @@ private bool OnHandleReplayMessage(object message, bool skipEffects) switch (message) { - case ChatMessage chat: - // Pass the chat message to the UI controller, skip the speech-bubble / pop-up. - _uiMan.GetUIController().ProcessChatMessage(chat, speechBubble: false); - return true; case RoundEndMessageEvent: case PopupEvent: case AudioMessage: diff --git a/Content.Client/Spawners/ClientEntitySpawnerComponent.cs b/Content.Client/Spawners/ClientEntitySpawnerComponent.cs deleted file mode 100644 index c9a32c1a2cf5eb..00000000000000 --- a/Content.Client/Spawners/ClientEntitySpawnerComponent.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Generic; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Serialization.Manager.Attributes; - -namespace Content.Client.Spawners -{ - /// - /// Spawns a set of entities on the client only, and removes them when this component is removed. - /// - [RegisterComponent] - [ComponentProtoName("ClientEntitySpawner")] - public sealed class ClientEntitySpawnerComponent : Component - { - [Dependency] private readonly IEntityManager _entMan = default!; - - [DataField("prototypes")] private List _prototypes = new() { "HVDummyWire" }; - - private readonly List _entity = new(); - - protected override void Initialize() - { - base.Initialize(); - SpawnEntities(); - } - - protected override void OnRemove() - { - RemoveEntities(); - base.OnRemove(); - } - - private void SpawnEntities() - { - foreach (var proto in _prototypes) - { - var entity = _entMan.SpawnEntity(proto, _entMan.GetComponent(Owner).Coordinates); - _entity.Add(entity); - } - } - - private void RemoveEntities() - { - foreach (var entity in _entity) - { - _entMan.DeleteEntity(entity); - } - } - } -} diff --git a/Content.Client/UserInterface/ControlExtensions/ControlExtension.cs b/Content.Client/UserInterface/ControlExtensions/ControlExtension.cs new file mode 100644 index 00000000000000..c0e4a038a1a1e0 --- /dev/null +++ b/Content.Client/UserInterface/ControlExtensions/ControlExtension.cs @@ -0,0 +1,96 @@ +using Content.Client.Guidebook.Controls; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client.UserInterface.ControlExtensions; + +public static class ControlExtension +{ + public static List GetControlOfType(this Control parent) where T : Control + { + return parent.GetControlOfType(typeof(T).Name, false); + } + public static List GetControlOfType(this Control parent, string childType) where T : Control + { + return parent.GetControlOfType(childType, false); + } + + public static List GetControlOfType(this Control parent, bool fullTreeSearch) where T : Control + { + return parent.GetControlOfType(typeof(T).Name, fullTreeSearch); + } + + public static List GetControlOfType(this Control parent, string childType, bool fullTreeSearch) where T : Control + { + List controlList = new List(); + + foreach (var child in parent.Children) + { + var isType = child.GetType().Name == childType; + var hasChildren = child.ChildCount > 0; + + var searchDeeper = hasChildren && !isType; + + if (isType) + { + controlList.Add((T) child); + } + + if (fullTreeSearch || searchDeeper) + { + controlList.AddRange(child.GetControlOfType(childType, fullTreeSearch)); + } + } + + return controlList; + } + + public static List GetSearchableControls(this Control parent, bool fullTreeSearch = false) + { + List controlList = new List(); + + foreach (var child in parent.Children) + { + var hasChildren = child.ChildCount > 0; + var searchDeeper = hasChildren && child is not ISearchableControl; + + if (child is ISearchableControl searchableChild) + { + controlList.Add(searchableChild); + } + + if (fullTreeSearch || searchDeeper) + { + controlList.AddRange(child.GetSearchableControls(fullTreeSearch)); + } + } + + return controlList; + } + + public static bool ChildrenContainText(this Control parent, string search) + { + var labels = parent.GetControlOfType