Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

using System.Linq;
using Content.Client._DV.CustomObjectiveSummary;
using Content.Client.CharacterInfo;
using Content.Client.Gameplay;
using Content.Client.Stylesheets;
Expand Down Expand Up @@ -56,6 +57,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
[Dependency] private readonly IEntityManager _ent = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly CustomObjectiveSummaryUIController _objective = default!; // DeltaV

[UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!;
[UISystemDependency] private readonly SpriteSystem _sprite = default!;
Expand Down Expand Up @@ -205,6 +207,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)
{
Expand Down Expand Up @@ -281,4 +296,4 @@ private void ToggleWindow()
_window.Open();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2025 beck-thompson <107373427+beck-thompson@users.noreply.github.com>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

using Content.Shared._DV.CustomObjectiveSummary;
using Robust.Client.UserInterface.Controllers;
using Robust.Shared.Network;

namespace Content.Client._DV.CustomObjectiveSummary;

public sealed class CustomObjectiveSummaryUIController : UIController
{
[Dependency] private readonly IClientNetManager _net = default!;

private CustomObjectiveSummaryWindow? _window;

public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<CustomObjectiveSummaryOpenMessage>(OnCustomObjectiveSummaryOpen);
}

private void OnCustomObjectiveSummaryOpen(CustomObjectiveSummaryOpenMessage msg, EntitySessionEventArgs args)
{
OpenWindow();
}

public void OpenWindow()
{
// If a window is already open, close it
_window?.Close();

_window = new CustomObjectiveSummaryWindow();
_window.OpenCentered();
_window.OnClose += () => _window = null;
_window.OnSubmitted += OnFeedbackSubmitted;
}

private void OnFeedbackSubmitted(string args)
{
var msg = new CustomObjectiveClientSetObjective
{
Summary = args,
};
_net.ClientSendMessage(msg);
_window?.Close();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'custom-objective-window-title'}"
MinSize="300 250"
SetSize="550 370">
<BoxContainer Orientation="Vertical" Margin="10 10 20 0">
<Label HorizontalAlignment="Center" Text="{Loc 'custom-objective-window-explain'}" />
<TextEdit Name="ObjectiveSummaryTextEdit" MaxHeight="500" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" MinHeight="200" />
<Label Name="CharacterLimitLabel" HorizontalAlignment="Center" StyleClasses="LabelSmall"/>
<Label HorizontalAlignment="Center" Text="{Loc 'custom-objective-window-explain-edit'}" />
<controls:ConfirmButton Name="SubmitButton" ConfirmationText="{Loc 'custom-objective-window-submit-button-text-confirm'}" Text="{Loc 'custom-objective-window-submit-button-text'}" Margin="0 10 0 10" />
</BoxContainer>
</controls:FancyWindow>
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2025 beck-thompson <107373427+beck-thompson@users.noreply.github.com>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

using Content.Client.UserInterface.Controls;
using Content.Shared.Mind;
using Robust.Client.AutoGenerated;
using Robust.Client.Player;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;

namespace Content.Client._DV.CustomObjectiveSummary;

[GenerateTypedNameReferences]
public sealed partial class CustomObjectiveSummaryWindow : FancyWindow
{
[Dependency] private readonly IPlayerManager _players = default!;
[Dependency] private readonly IEntityManager _entity = default!;

private SharedMindSystem? _mind;

private readonly int _maxLength = 1024;

public event Action<string>? OnSubmitted;

public CustomObjectiveSummaryWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);

SubmitButton.OnPressed +=
_ => OnSubmitted?.Invoke(Rope.Collapse(ObjectiveSummaryTextEdit.TextRope));
ObjectiveSummaryTextEdit.OnTextChanged += _ => UpdateWordCount();

_mind ??= _entity.System<SharedMindSystem>();

_mind.TryGetMind(_players.LocalSession, out var mindUid, out _);

UpdateWordCount();

if (_entity.TryGetComponent<Shared._DV.CustomObjectiveSummary.CustomObjectiveSummaryComponent>(mindUid, out var summary))
ObjectiveSummaryTextEdit.TextRope = new Rope.Leaf(summary.ObjectiveSummary);

UpdateWordCount();
}

private void UpdateWordCount()
{
// Disable the button if its over the max length.sa
SubmitButton.Disabled = ObjectiveSummaryTextEdit.TextLength > _maxLength;
CharacterLimitLabel.Text = ObjectiveSummaryTextEdit.TextLength + "/" + _maxLength;
}
}
33 changes: 33 additions & 0 deletions Content.Server/Objectives/ObjectivesSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
using Content.Goobstation.Common.ServerCurrency;
using Content.Goobstation.Shared.ManifestListings;
using Content.Server.Objectives.Commands;
using Content.Shared._DV.CustomObjectiveSummary;
using Content.Shared.CCVar;
using Content.Shared.Prototypes;
using Content.Shared.Roles.Jobs;
Expand Down Expand Up @@ -242,6 +243,8 @@ private void AddSummary(StringBuilder result, string agent, List<(EntityUid, str
$"{username:subject} achieved {progress}% of objective {objectiveTitle}");

agentSummary.Append("- ");
// Omu: if you're going back to good ol' pinktext after green+pinktext drops,
// start a multiline comment starting here.
if (!_showGreentext)
{
agentSummary.AppendLine(objectiveTitle);
Expand Down Expand Up @@ -292,10 +295,40 @@ private void AddSummary(StringBuilder result, string agent, List<(EntityUid, str
("progress", progress)
));
}
// Omu: future multiline comment ends here
// Begin DeltaV Additions - Generic objective
/* Omu: Green+PinkText
agentSummary.AppendLine(Loc.GetString(
"objectives-objective",
("objective", objectiveTitle)
));
*/
}
}

var successRate = totalObjectives > 0 ? (float)completedObjectives / totalObjectives : 0f;
// Begin DeltaV Additions - custom objective response.
if (TryComp<CustomObjectiveSummaryComponent>(mindId, out var customComp))
{
// 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 = FormattedMessage.EscapeText(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));
}

Expand Down
2 changes: 2 additions & 0 deletions Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
using Content.Server.Shuttles.Events;
using Content.Server.Station.Events;
using Content.Server.Station.Systems;
using Content.Shared._DV.CustomObjectiveSummary; // DeltaV
using Content.Shared.Access.Systems;
using Content.Shared.CCVar;
using Content.Shared.Database;
Expand Down Expand Up @@ -287,6 +288,7 @@ private void OnEmergencyFTL(EntityUid uid, EmergencyShuttleComponent component,
};
_deviceNetworkSystem.QueuePacket(uid, null, payload, netComp.TransmitFrequency);
}
RaiseLocalEvent(new EvacShuttleLeftEvent()); // DeltaV
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2025 beck-thompson <107373427+beck-thompson@users.noreply.github.com>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

using Content.Server.Administration.Logs;
using Content.Shared._DV.CustomObjectiveSummary;
using Content.Shared.Database;
using Content.Shared.Mind;
using Robust.Shared.Network;

namespace Content.Server._DV.CustomObjectiveSummary;

public sealed class CustomObjectiveSummarySystem : EntitySystem
{
[Dependency] private readonly IServerNetManager _net = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly IAdminLogManager _adminLog = default!;

public override void Initialize()
{
SubscribeLocalEvent<EvacShuttleLeftEvent>(OnEvacShuttleLeft);

_net.RegisterNetMessage<CustomObjectiveClientSetObjective>(OnCustomObjectiveFeedback);
}

private void OnCustomObjectiveFeedback(CustomObjectiveClientSetObjective msg)
{
if (!_mind.TryGetMind(msg.MsgChannel.UserId, out var mind))
return;

if (mind.Value.Comp.Objectives.Count == 0)
return;

var comp = EnsureComp<CustomObjectiveSummaryComponent>(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();

// Assumes the assistant is still there at the end of the round.
foreach (var mind in allMinds)
{
// Only send the popup to people with objectives.
if (mind.Comp.Objectives.Count == 0)
continue;

RaiseNetworkEvent(new CustomObjectiveSummaryOpenMessage(), mind.Owner);
}
}
}
1 change: 1 addition & 0 deletions Content.Shared.Database/LogType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ public enum LogType
/// Tiles related interactions.
/// </summary>
Tile = 86,
ObjectiveSummary = 422, // DeltaV

/// <summary>
/// A client has sent too many chat messages recently and is temporarily blocked from sending more.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2025 beck-thompson <107373427+beck-thompson@users.noreply.github.com>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

using Robust.Shared.GameStates;

namespace Content.Shared._DV.CustomObjectiveSummary;

/// <summary>
/// Put on a players mind if the wrote a custom summary for their objectives.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class CustomObjectiveSummaryComponent : Component
{
/// <summary>
/// What the player wrote as their summary!
/// </summary>
[DataField, AutoNetworkedField]
public string ObjectiveSummary = "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2025 beck-thompson <107373427+beck-thompson@users.noreply.github.com>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

using Lidgren.Network;
using Robust.Shared.Network;
using Robust.Shared.Serialization;

namespace Content.Shared._DV.CustomObjectiveSummary;

/// <summary>
/// Message from the client with what they are updating their summary to.
/// </summary>
public sealed class CustomObjectiveClientSetObjective : NetMessage
{
public override MsgGroups MsgGroup => MsgGroups.EntityEvent;

/// <summary>
/// The summary that the user wrote.
/// </summary>
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;
}

/// <summary>
/// Clients listen for this event and when they get it, they open a popup so the player can fill out the objective summary.
/// </summary>
[Serializable, NetSerializable]
public sealed class CustomObjectiveSummaryOpenMessage : EntityEventArgs;

/// <summary>
/// DeltaV event for when the evac shuttle leaves.
/// </summary>
[Serializable, NetSerializable]
public sealed class EvacShuttleLeftEvent : EventArgs;
Original file line number Diff line number Diff line change
@@ -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-submit-button-text-confirm = Confirm submission
custom-objective-window-explain = Explain how you completed your objectives 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]
Loading