diff --git a/Content.Client/UserInterface/Systems/Character/CharacterUIController.cs b/Content.Client/UserInterface/Systems/Character/CharacterUIController.cs index c081fbd876c..0c64d73ea36 100644 --- a/Content.Client/UserInterface/Systems/Character/CharacterUIController.cs +++ b/Content.Client/UserInterface/Systems/Character/CharacterUIController.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Client._DVA.DVCustomObjectiveSummary; // DeltaV using Content.Client.CharacterInfo; using Content.Client.Gameplay; using Content.Client.Stylesheets; @@ -27,17 +28,23 @@ namespace Content.Client.UserInterface.Systems.Character; [UsedImplicitly] public sealed partial class CharacterUIController : UIController, IOnStateEntered, IOnStateExited, IOnSystemChanged { - [Dependency] private IEntityManager _ent = default!; - [Dependency] private IPlayerManager _player = default!; - [Dependency] private IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntityManager _ent = default!; + [Dependency] private readonly ILogManager _logMan = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly DVCustomObjectiveSummaryUIController _objective = default!; // DeltaV [UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!; [UISystemDependency] private readonly SpriteSystem _sprite = default!; + private ISawmill _sawmill = default!; + public override void Initialize() { base.Initialize(); + _sawmill = _logMan.GetSawmill("character"); + SubscribeNetworkEvent(OnRoleTypeChanged); } @@ -179,6 +186,19 @@ private void CharacterUpdated(CharacterData data) _window.Objectives.AddChild(objectiveControl); } + // Begin DeltaV Additions - Custom objective summary + if (objectives.Count > 0) + { + var button = new Button + { + Text = Loc.GetString("custom-objective-button-text"), + Margin = new Thickness(0, 10, 0, 10) + }; + button.OnPressed += _ => _objective.OpenWindow(); + + _window.Objectives.AddChild(button); + } + // End DeltaV Additions if (briefing != null) { diff --git a/Content.Client/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummaryUIController.cs b/Content.Client/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummaryUIController.cs new file mode 100644 index 00000000000..d21fa185616 --- /dev/null +++ b/Content.Client/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummaryUIController.cs @@ -0,0 +1,44 @@ +using Content.Shared._DVA.DVCustomObjectiveSummary; +using Robust.Client.UserInterface.Controllers; +using Robust.Shared.Network; + +namespace Content.Client._DVA.DVCustomObjectiveSummary; + +public sealed class DVCustomObjectiveSummaryUIController : UIController +{ + [Dependency] private readonly IClientNetManager _net = default!; + + private DVCustomObjectiveSummaryWindow? _window; + + public override void Initialize() + { + base.Initialize(); + SubscribeNetworkEvent(OnDVCustomObjectiveSummaryOpen); + } + + private void OnDVCustomObjectiveSummaryOpen(DVCustomObjectiveSummaryOpenMessage msg, EntitySessionEventArgs args) + { + OpenWindow(); + } + + public void OpenWindow() + { + // If a window is already open, close it + _window?.Close(); + + _window = new DVCustomObjectiveSummaryWindow(); + _window.OpenCentered(); + _window.OnClose += () => _window = null; + _window.OnSubmitted += OnFeedbackSubmitted; + } + + private void OnFeedbackSubmitted(string args) + { + var msg = new DVCustomObjectiveClientSetObjective + { + Summary = args, + }; + _net.ClientSendMessage(msg); + _window?.Close(); + } +} diff --git a/Content.Client/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummaryWindow.xaml b/Content.Client/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummaryWindow.xaml new file mode 100644 index 00000000000..1e28d96a020 --- /dev/null +++ b/Content.Client/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummaryWindow.xaml @@ -0,0 +1,13 @@ + + + + diff --git a/Content.Client/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummaryWindow.xaml.cs b/Content.Client/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummaryWindow.xaml.cs new file mode 100644 index 00000000000..42434e98542 --- /dev/null +++ b/Content.Client/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummaryWindow.xaml.cs @@ -0,0 +1,66 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared._DVA.DCCVars; +using Content.Shared.Mind; +using Robust.Client.AutoGenerated; +using Robust.Client.Player; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Configuration; +using Robust.Shared.Utility; + +namespace Content.Client._DVA.DVCustomObjectiveSummary; + +[GenerateTypedNameReferences] +public sealed partial class DVCustomObjectiveSummaryWindow : FancyWindow +{ + [Dependency] private readonly IPlayerManager _players = default!; + [Dependency] private readonly IEntityManager _entity = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + + private SharedMindSystem? _mind; + + // Maximum length the summary can be in characters. + private int _maxLengthSummaryLength; + + public event Action? OnSubmitted; + + public DVCustomObjectiveSummaryWindow() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + _cfg.OnValueChanged(DCCVars.MaxObjectiveSummaryLength, len => + { + _maxLengthSummaryLength = len; + UpdateWordCount(); + }, + invokeImmediately: true); + + + SubmitButton.OnPressed += + _ => OnSubmitted?.Invoke(Rope.Collapse(ObjectiveSummaryTextEdit.TextRope)); + ObjectiveSummaryTextEdit.OnTextChanged += _ => UpdateWordCount(); + + _mind ??= _entity.System(); + + _mind.TryGetMind(_players.LocalSession, out var mindUid, out _); + + // This is only for if you quit the server then rejoin. + if (_entity.TryGetComponent(mindUid, out var summary)) + ObjectiveSummaryTextEdit.TextRope = new Rope.Leaf(summary.ObjectiveSummary); + + UpdateWordCount(); + } + + private void UpdateWordCount() + { + var textLength = ObjectiveSummaryTextEdit.TextLength; + var overMax = textLength > _maxLengthSummaryLength; + + // Disable the button if it's over the max length. + SubmitButton.Disabled = overMax; + CharacterLimitLabel.Text = textLength + "/" + _maxLengthSummaryLength; + + CharacterLimitLabel.FontColorOverride = overMax ? Color.Red : null; + PlaceholderText.Visible = textLength == 0; + } +} diff --git a/Content.Server/Objectives/ObjectivesSystem.cs b/Content.Server/Objectives/ObjectivesSystem.cs index c2efbd8e4e8..5dd31babf18 100644 --- a/Content.Server/Objectives/ObjectivesSystem.cs +++ b/Content.Server/Objectives/ObjectivesSystem.cs @@ -12,6 +12,8 @@ using System.Linq; using System.Text; using Content.Server.Objectives.Commands; +using Content.Shared._DVA.DCCVars; // DeltaV - Pinktext +using Content.Shared._DVA.DVCustomObjectiveSummary; // DeltaV Pinktext using Content.Shared.CCVar; using Content.Shared.Prototypes; using Content.Shared.Roles.Jobs; @@ -33,6 +35,8 @@ public sealed partial class ObjectivesSystem : SharedObjectivesSystem private bool _showGreentext; + private int _maxLengthSummaryLength; // DeltaV + public override void Initialize() { base.Initialize(); @@ -41,6 +45,8 @@ public override void Initialize() Subs.CVar(_cfg, CCVars.GameShowGreentext, value => _showGreentext = value, true); + Subs.CVar(_cfg, DCCVars.MaxObjectiveSummaryLength, len => _maxLengthSummaryLength = len, true); // DeltaV + ProtoMan.PrototypesReloaded += CreateCompletions; } @@ -170,47 +176,59 @@ private void AddSummary(StringBuilder result, string agent, List<(EntityUid, str totalObjectives++; agentSummary.Append("- "); - if (!_showGreentext) - { - agentSummary.AppendLine(objectiveTitle); - } - else if (progress > 0.99f) + /* Begin DeltaV removal - Removed greentext + if (progress > 0.99f) { agentSummary.AppendLine(Loc.GetString( "objectives-objective-success", ("objective", objectiveTitle), - ("progress", progress) + ("markupColor", "green") )); completedObjectives++; } - else if (progress <= 0.99f && progress >= 0.5f) - { - agentSummary.AppendLine(Loc.GetString( - "objectives-objective-partial-success", - ("objective", objectiveTitle), - ("progress", progress) - )); - } - else if (progress < 0.5f && progress > 0f) - { - agentSummary.AppendLine(Loc.GetString( - "objectives-objective-partial-failure", - ("objective", objectiveTitle), - ("progress", progress) - )); - } else { agentSummary.AppendLine(Loc.GetString( "objectives-objective-fail", ("objective", objectiveTitle), - ("progress", progress) + ("progress", (int) (progress * 100)), + ("markupColor", "red") )); } + End DeltaV removal */ + // Begin DeltaV Additions - Generic objective + agentSummary.AppendLine(Loc.GetString( + "objectives-objective", + ("objective", objectiveTitle) + )); + // End DeltaV Additions } } var successRate = totalObjectives > 0 ? (float) completedObjectives / totalObjectives : 0f; + // Begin DeltaV Additions - custom objective response. + if (TryComp(mindId, out var customComp) && + customComp.ObjectiveSummary.Length <= _maxLengthSummaryLength) + { + // We have to spit it like this to make it readable. Yeah, it sucks but for some reason the entire thing + // is just one long string... + var words = customComp.ObjectiveSummary.Split(" "); + var currentLine = ""; + foreach (var word in words) + { + currentLine += word + " "; + + // magic number + if (currentLine.Length <= 50) + continue; + + agentSummary.AppendLine(Loc.GetString("custom-objective-format", ("line", currentLine))); + currentLine = ""; + } + + agentSummary.AppendLine(Loc.GetString("custom-objective-format", ("line", currentLine))); + } + // End DeltaV Additions agentSummaries.Add((agentSummary.ToString(), successRate, completedObjectives)); } diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs index e0cd4a155f2..d42004e255a 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs @@ -16,6 +16,7 @@ using Content.Server.Shuttles.Events; using Content.Server.Station.Events; using Content.Server.Station.Systems; +using Content.Shared._DVA.DVCustomObjectiveSummary; // DeltaV using Content.Shared.Access.Systems; using Content.Shared.CCVar; using Content.Shared.Database; @@ -221,6 +222,7 @@ private void OnEmergencyFTL(EntityUid uid, EmergencyShuttleComponent component, }; _deviceNetworkSystem.QueuePacket(uid, null, payload, netComp.TransmitFrequency); } + RaiseLocalEvent(new EvacShuttleLeftEvent()); // DeltaV } /// diff --git a/Content.Server/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummarySystem.cs b/Content.Server/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummarySystem.cs new file mode 100644 index 00000000000..9a98bb14024 --- /dev/null +++ b/Content.Server/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummarySystem.cs @@ -0,0 +1,55 @@ +using Content.Server.Administration.Logs; +using Content.Shared._DVA.DVCustomObjectiveSummary; +using Content.Shared.Database; +using Content.Shared.Mind; +using Robust.Shared.Network; + +namespace Content.Server._DVA.DVCustomObjectiveSummary; + +public sealed class DVCustomObjectiveSummarySystem : EntitySystem +{ + [Dependency] private readonly IServerNetManager _net = default!; + [Dependency] private readonly ISharedPlayerManager _player = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly IAdminLogManager _adminLog = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnEvacShuttleLeft); + + _net.RegisterNetMessage(OnDVCustomObjectiveFeedback); + } + + private void OnDVCustomObjectiveFeedback(DVCustomObjectiveClientSetObjective msg) + { + if (!_mind.TryGetMind(msg.MsgChannel.UserId, out var mind)) + return; + + if (mind.Value.Comp.Objectives.Count == 0) + return; + + var comp = EnsureComp(mind.Value); + + comp.ObjectiveSummary = msg.Summary; + Dirty(mind.Value.Owner, comp); + + _adminLog.Add(LogType.ObjectiveSummary, $"{ToPrettyString(mind.Value.Comp.OwnedEntity)} wrote objective summery: {msg.Summary}"); + } + + private void OnEvacShuttleLeft(EvacShuttleLeftEvent args) + { + var allMinds = _mind.GetAliveHumans(); + + foreach (var mind in allMinds) + { + // Only send the popup to people with objectives. + if (mind.Comp.Objectives.Count == 0) + continue; + + if (!_player.TryGetSessionById(mind.Comp.UserId, out var session)) + continue; + + RaiseNetworkEvent(new DVCustomObjectiveSummaryOpenMessage(), session); + } + } +} diff --git a/Content.Shared.Database/LogType.cs b/Content.Shared.Database/LogType.cs index e5b9fad0263..116cc94217b 100644 --- a/Content.Shared.Database/LogType.cs +++ b/Content.Shared.Database/LogType.cs @@ -391,6 +391,9 @@ public enum LogType /// Tiles related interactions. /// Tile = 86, + BagOfHolding = 420, // DeltaV - Summary: adds bag of holding. + Psionics = 421, // DeltaV - Summary: adds psionic as a log type. + ObjectiveSummary = 422, // DeltaV /// /// A client has sent too many chat messages recently and is temporarily blocked from sending more. diff --git a/Content.Shared/_DVA/CCVars/DCCVars.cs b/Content.Shared/_DVA/CCVars/DCCVars.cs index ffdef39e659..c174e73402e 100644 --- a/Content.Shared/_DVA/CCVars/DCCVars.cs +++ b/Content.Shared/_DVA/CCVars/DCCVars.cs @@ -23,4 +23,10 @@ public sealed partial class DCCVars /// public static readonly CVarDef SsdIndicatorRecentAfterSeconds = CVarDef.Create("deltav.ssd.recent_after_seconds", 300f, CVar.SERVER | CVar.REPLICATED); + + /// + /// Maximum number of characters in objective summaries. + /// + public static readonly CVarDef MaxObjectiveSummaryLength = + CVarDef.Create("game.max_objective_summary_length", 256, CVar.SERVER | CVar.REPLICATED); } diff --git a/Content.Shared/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummaryComponent.cs b/Content.Shared/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummaryComponent.cs new file mode 100644 index 00000000000..b066165bca4 --- /dev/null +++ b/Content.Shared/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummaryComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DVA.DVCustomObjectiveSummary; + +/// +/// Put on a players mind if the wrote a custom summary for their objectives. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class DVCustomObjectiveSummaryComponent : Component +{ + /// + /// What the player wrote as their summary! + /// + [DataField, AutoNetworkedField] + public string ObjectiveSummary = ""; +} diff --git a/Content.Shared/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummaryEvents.cs b/Content.Shared/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummaryEvents.cs new file mode 100644 index 00000000000..7e715f70799 --- /dev/null +++ b/Content.Shared/_DVA/DVCustomObjectiveSummary/DVCustomObjectiveSummaryEvents.cs @@ -0,0 +1,42 @@ +using Lidgren.Network; +using Robust.Shared.Network; +using Robust.Shared.Serialization; + +namespace Content.Shared._DVA.DVCustomObjectiveSummary; + +/// +/// Message from the client with what they are updating their summary to. +/// +public sealed class DVCustomObjectiveClientSetObjective : NetMessage +{ + public override MsgGroups MsgGroup => MsgGroups.EntityEvent; + + /// + /// The summary that the user wrote. + /// + public string Summary = string.Empty; + + public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) + { + Summary = buffer.ReadString(); + } + + public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) + { + buffer.Write(Summary); + } + + public override NetDeliveryMethod DeliveryMethod => NetDeliveryMethod.ReliableUnordered; +} + +/// +/// Clients listen for this event and when they get it, they open a popup so the player can fill out the objective summary. +/// +[Serializable, NetSerializable] +public sealed class DVCustomObjectiveSummaryOpenMessage : EntityEventArgs; + +/// +/// DeltaV event for when the evac shuttle leaves. +/// +[Serializable, NetSerializable] +public sealed class EvacShuttleLeftEvent : EventArgs; diff --git a/Resources/Locale/en-US/_DVA/dvcustomobjectivesummary/dvcustomobjectivesummary.ftl b/Resources/Locale/en-US/_DVA/dvcustomobjectivesummary/dvcustomobjectivesummary.ftl new file mode 100644 index 00000000000..3c62d3965df --- /dev/null +++ b/Resources/Locale/en-US/_DVA/dvcustomobjectivesummary/dvcustomobjectivesummary.ftl @@ -0,0 +1,13 @@ +custom-objective-button-text = Write objective summary + +# UI +custom-objective-window-title = Custom objective summary +custom-objective-window-submit-button-text = Submit +custom-objective-window-explain = Explain how you completed your objectives here! +custom-objective-window-explain-type-here = Type here! +custom-objective-window-explain-edit = You can always edit this anytime before the round ends. + +objectives-objective = {$objective} + +# End of round +custom-objective-format = [color=#FFAEC9] {$line}[/color]