forked from space-syndicate/space-station-14
-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Medals * Fixes
- Loading branch information
Showing
10 changed files
with
525 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using Content.Client._CorvaxNext.CrewMedal.UI; | ||
using Content.Shared._CorvaxNext.CrewMedal; | ||
|
||
namespace Content.Client._CorvaxNext.CrewMedal; | ||
|
||
/// <summary> | ||
/// Handles the client-side logic for the Crew Medal system. | ||
/// </summary> | ||
public sealed class CrewMedalSystem : SharedCrewMedalSystem | ||
{ | ||
[Dependency] private readonly SharedUserInterfaceSystem _userInterfaceSystem = default!; | ||
|
||
public override void Initialize() | ||
{ | ||
base.Initialize(); | ||
// Subscribes to the event triggered after the state is automatically handled. | ||
SubscribeLocalEvent<CrewMedalComponent, AfterAutoHandleStateEvent>(OnCrewMedalAfterState); | ||
} | ||
|
||
/// <summary> | ||
/// When an updated state is received on the client, refresh the UI to display the latest data. | ||
/// </summary> | ||
private void OnCrewMedalAfterState(Entity<CrewMedalComponent> entity, ref AfterAutoHandleStateEvent args) | ||
{ | ||
// Checks if the Crew Medal UI is open for the given entity and reloads it with updated data. | ||
if (_userInterfaceSystem.TryGetOpenUi<CrewMedalBoundUserInterface>( | ||
entity.Owner, | ||
CrewMedalUiKey.Key, | ||
out var medalUi)) | ||
{ | ||
medalUi.Reload(); | ||
} | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
Content.Client/_CorvaxNext/CrewMedal/UI/CrewMedalBoundUserInterface.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
using Content.Shared._CorvaxNext.CrewMedal; | ||
using Robust.Client.UserInterface; | ||
|
||
namespace Content.Client._CorvaxNext.CrewMedal.UI; | ||
|
||
/// <summary> | ||
/// A wrapper class for the Crew Medal user interface. | ||
/// Initializes the <see cref="CrewMedalWindow"/> and updates it when new data is received from the server. | ||
/// </summary> | ||
public sealed class CrewMedalBoundUserInterface : BoundUserInterface | ||
{ | ||
[Dependency] private readonly IEntityManager _entityManager = default!; | ||
|
||
/// <summary> | ||
/// The main interface window. | ||
/// </summary> | ||
[ViewVariables] | ||
private CrewMedalWindow? _window; | ||
|
||
public CrewMedalBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) | ||
{ | ||
IoCManager.InjectDependencies(this); | ||
} | ||
|
||
protected override void Open() | ||
{ | ||
base.Open(); | ||
|
||
_window = this.CreateWindow<CrewMedalWindow>(); | ||
_window.OnReasonChanged += HandleReasonChanged; | ||
|
||
Reload(); | ||
} | ||
|
||
/// <summary> | ||
/// Called when the reason is changed in the <see cref="CrewMedalWindow"/>. | ||
/// Sends a message to the server with the new reason if it differs from the current one. | ||
/// </summary> | ||
private void HandleReasonChanged(string newReason) | ||
{ | ||
if (!_entityManager.TryGetComponent<CrewMedalComponent>(Owner, out var component)) | ||
return; | ||
|
||
if (!component.Reason.Equals(newReason)) | ||
{ | ||
SendPredictedMessage(new CrewMedalReasonChangedMessage(newReason)); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Updates the data in the window to reflect the current state of the <see cref="CrewMedalComponent"/>. | ||
/// </summary> | ||
public void Reload() | ||
{ | ||
if (_window is null) | ||
return; | ||
|
||
if (!_entityManager.TryGetComponent<CrewMedalComponent>(Owner, out var component)) | ||
return; | ||
|
||
_window.SetCurrentReason(component.Reason); | ||
_window.SetAwarded(component.Awarded); | ||
_window.SetMaxCharacters(component.MaxCharacters); | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
Content.Client/_CorvaxNext/CrewMedal/UI/CrewMedalWindow.xaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<DefaultWindow xmlns="https://spacestation14.io" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
Title="{Loc 'crew-medal-ui-header'}"> | ||
<BoxContainer Orientation="Vertical" SeparationOverride="4" MinWidth="150"> | ||
<Label Text="{Loc 'crew-medal-ui-reason'}" /> | ||
<BoxContainer Orientation="Horizontal"> | ||
<LineEdit Name="ReasonLineEdit" HorizontalExpand="True" /> | ||
<Button Name="SaveButton" Text="{Loc 'crew-medal-ui-save'}" /> | ||
</BoxContainer> | ||
<Label Name="CharacterLabel" Text="" /> | ||
<Label Text="{Loc 'crew-medal-ui-info'}" /> | ||
</BoxContainer> | ||
</DefaultWindow> |
88 changes: 88 additions & 0 deletions
88
Content.Client/_CorvaxNext/CrewMedal/UI/CrewMedalWindow.xaml.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
using Robust.Client.AutoGenerated; | ||
using Robust.Client.UserInterface.CustomControls; | ||
using Robust.Client.UserInterface.XAML; | ||
|
||
namespace Content.Client._CorvaxNext.CrewMedal.UI; | ||
|
||
[GenerateTypedNameReferences] | ||
public sealed partial class CrewMedalWindow : DefaultWindow | ||
{ | ||
/// <summary> | ||
/// Event triggered when the "Save" button is pressed, | ||
/// provided the user has changed the reason text. | ||
/// </summary> | ||
public event Action<string>? OnReasonChanged; | ||
|
||
private bool _isFocused; | ||
private string _reason = string.Empty; | ||
private bool _awarded; | ||
private int _maxCharacters = 50; | ||
|
||
public CrewMedalWindow() | ||
{ | ||
RobustXamlLoader.Load(this); | ||
|
||
ReasonLineEdit.OnTextChanged += _ => | ||
{ | ||
// Check character limit and award status | ||
SaveButton.Disabled = _awarded || ReasonLineEdit.Text.Length > _maxCharacters; | ||
CharacterLabel.Text = Loc.GetString( | ||
"crew-medal-ui-character-limit", | ||
("number", ReasonLineEdit.Text.Length), | ||
("max", _maxCharacters)); | ||
}; | ||
|
||
ReasonLineEdit.OnFocusEnter += _ => _isFocused = true; | ||
ReasonLineEdit.OnFocusExit += _ => _isFocused = false; | ||
|
||
SaveButton.OnPressed += _ => | ||
{ | ||
OnReasonChanged?.Invoke(ReasonLineEdit.Text); | ||
SaveButton.Disabled = true; | ||
}; | ||
|
||
// Initialize the character counter display | ||
CharacterLabel.Text = Loc.GetString( | ||
"crew-medal-ui-character-limit", | ||
("number", ReasonLineEdit.Text.Length), | ||
("max", _maxCharacters)); | ||
} | ||
|
||
/// <summary> | ||
/// Sets the current reason and synchronizes it with the input field | ||
/// if the user is not currently editing the field. | ||
/// </summary> | ||
public void SetCurrentReason(string reason) | ||
{ | ||
if (_reason == reason) | ||
return; | ||
|
||
_reason = reason; | ||
|
||
// Synchronize text if the input field is not focused | ||
if (!_isFocused) | ||
ReasonLineEdit.Text = _reason; | ||
} | ||
|
||
/// <summary> | ||
/// Updates the "is medal awarded" status | ||
/// and disables editing if the medal is already awarded. | ||
/// </summary> | ||
public void SetAwarded(bool awarded) | ||
{ | ||
_awarded = awarded; | ||
ReasonLineEdit.Editable = !_awarded; | ||
SaveButton.Disabled = _awarded; | ||
} | ||
|
||
/// <summary> | ||
/// Updates the maximum character limit for the reason. | ||
/// If the current text exceeds the limit, it will be truncated. | ||
/// </summary> | ||
public void SetMaxCharacters(int number) | ||
{ | ||
_maxCharacters = number; | ||
if (ReasonLineEdit.Text.Length > _maxCharacters) | ||
ReasonLineEdit.Text = ReasonLineEdit.Text[.._maxCharacters]; | ||
} | ||
} |
137 changes: 137 additions & 0 deletions
137
Content.Server/_CorvaxNext/CrewMedal/CrewMedalSystem.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
using Content.Server.GameTicking; | ||
using Content.Shared.Administration.Logs; | ||
using Content.Shared.Clothing; | ||
using Content.Shared._CorvaxNext.CrewMedal; | ||
using Content.Shared.Database; | ||
using Content.Shared.IdentityManagement; | ||
using Content.Shared.Popups; | ||
using System.Linq; | ||
using System.Text; | ||
|
||
namespace Content.Server._CorvaxNext.CrewMedal; | ||
|
||
public sealed class CrewMedalSystem : SharedCrewMedalSystem | ||
{ | ||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; | ||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; | ||
|
||
public override void Initialize() | ||
{ | ||
base.Initialize(); | ||
SubscribeLocalEvent<CrewMedalComponent, ClothingGotEquippedEvent>(OnMedalEquipped); | ||
SubscribeLocalEvent<CrewMedalComponent, CrewMedalReasonChangedMessage>(OnMedalReasonChanged); | ||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText); | ||
} | ||
|
||
/// <summary> | ||
/// Called when a medal is equipped on a character, indicating the medal has been awarded. | ||
/// </summary> | ||
private void OnMedalEquipped(Entity<CrewMedalComponent> medal, ref ClothingGotEquippedEvent args) | ||
{ | ||
if (medal.Comp.Awarded) | ||
return; | ||
|
||
medal.Comp.Recipient = Identity.Name(args.Wearer, EntityManager); | ||
medal.Comp.Awarded = true; | ||
Dirty(medal); | ||
|
||
// Display a popup about the award | ||
_popupSystem.PopupEntity( | ||
Loc.GetString( | ||
"comp-crew-medal-award-text", | ||
("recipient", medal.Comp.Recipient), | ||
("medal", Name(medal.Owner)) | ||
), | ||
medal.Owner | ||
); | ||
|
||
// Log the event | ||
_adminLogger.Add( | ||
LogType.Action, | ||
LogImpact.Low, | ||
$"{ToPrettyString(args.Wearer):player} was awarded the {ToPrettyString(medal.Owner):entity} with the reason \"{medal.Comp.Reason}\"." | ||
); | ||
} | ||
|
||
/// <summary> | ||
/// Called when the reason is updated in the interface (before the medal is awarded). | ||
/// </summary> | ||
private void OnMedalReasonChanged(EntityUid uid, CrewMedalComponent medalComp, CrewMedalReasonChangedMessage args) | ||
{ | ||
if (medalComp.Awarded) | ||
return; | ||
|
||
// Trim to the character limit and sanitize the input | ||
var maxLength = Math.Min(medalComp.MaxCharacters, args.Reason.Length); | ||
medalComp.Reason = Sanitize(args.Reason[..maxLength]); | ||
|
||
Dirty(uid, medalComp); | ||
|
||
// Log the update | ||
_adminLogger.Add( | ||
LogType.Action, | ||
LogImpact.Low, | ||
$"{ToPrettyString(args.Actor):user} set {ToPrettyString(uid):entity} with award reason \"{medalComp.Reason}\"." | ||
); | ||
} | ||
|
||
/// <summary> | ||
/// Adds a list of awarded medals to the round-end summary window. | ||
/// </summary> | ||
private void OnRoundEndText(RoundEndTextAppendEvent ev) | ||
{ | ||
var awardedMedals = new List<(string MedalName, string RecipientName, string Reason)>(); | ||
|
||
var query = EntityQueryEnumerator<CrewMedalComponent>(); | ||
while (query.MoveNext(out var uid, out var component)) | ||
{ | ||
if (component.Awarded) | ||
{ | ||
awardedMedals.Add( | ||
(Name(uid), component.Recipient, component.Reason) | ||
); | ||
} | ||
} | ||
|
||
if (awardedMedals.Count == 0) | ||
return; | ||
|
||
// Sort and convert to array | ||
var sortedMedals = awardedMedals.OrderBy(x => x.RecipientName).ToArray(); | ||
|
||
var result = new StringBuilder(); | ||
result.AppendLine( | ||
Loc.GetString( | ||
"comp-crew-medal-round-end-result", | ||
("count", sortedMedals.Length) | ||
) | ||
); | ||
|
||
foreach (var medal in sortedMedals) | ||
{ | ||
result.AppendLine( | ||
Loc.GetString( | ||
"comp-crew-medal-round-end-list", | ||
("medal", Sanitize(medal.MedalName)), | ||
("recipient", Sanitize(medal.RecipientName)), | ||
("reason", Sanitize(medal.Reason)) | ||
) | ||
); | ||
} | ||
|
||
ev.AddLine(result.AppendLine().ToString()); | ||
} | ||
|
||
/// <summary> | ||
/// Removes certain prohibited characters (e.g., brackets) | ||
/// to prevent unwanted tags in the text. | ||
/// </summary> | ||
private string Sanitize(string input) | ||
{ | ||
return input | ||
.Replace("[", string.Empty) | ||
.Replace("]", string.Empty) | ||
.Replace("{", string.Empty) | ||
.Replace("}", string.Empty); | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
Content.Shared/_CorvaxNext/CrewMedal/CrewMedalComponent.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
using Robust.Shared.GameStates; | ||
|
||
namespace Content.Shared._CorvaxNext.CrewMedal; | ||
|
||
/// <summary> | ||
/// Component for a medal that can be awarded to a player and | ||
/// will be displayed in the final round summary screen. | ||
/// </summary> | ||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] | ||
public sealed partial class CrewMedalComponent : Component | ||
{ | ||
/// <summary> | ||
/// The name of the recipient of the award. | ||
/// </summary> | ||
[AutoNetworkedField] | ||
[DataField] | ||
public string Recipient = string.Empty; | ||
|
||
/// <summary> | ||
/// The reason for the award. Can be set before the medal is awarded. | ||
/// </summary> | ||
[AutoNetworkedField] | ||
[DataField] | ||
public string Reason = string.Empty; | ||
|
||
/// <summary> | ||
/// If <c>true</c>, the medal is considered awarded, and the reason can no longer be changed. | ||
/// </summary> | ||
[AutoNetworkedField] | ||
[DataField] | ||
public bool Awarded; | ||
|
||
/// <summary> | ||
/// The maximum number of characters allowed for the reason. | ||
/// </summary> | ||
[AutoNetworkedField] | ||
[DataField] | ||
public int MaxCharacters = 50; | ||
} |
Oops, something went wrong.