diff --git a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
index 44c40143d83..04075000f5b 100644
--- a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
+++ b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
@@ -39,6 +39,6 @@ protected override void UpdateState(BoundUserInterfaceState message)
if (message is not CargoBountyConsoleState state)
return;
- _menu?.UpdateEntries(state.Bounties, state.UntilNextSkip);
+ _menu?.UpdateEntries(state.Bounties, state.History, state.UntilNextSkip);
}
}
diff --git a/Content.Client/Cargo/UI/BountyHistoryEntry.xaml b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml
new file mode 100644
index 00000000000..eee8c5cc165
--- /dev/null
+++ b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs
new file mode 100644
index 00000000000..f3c9bbfafb1
--- /dev/null
+++ b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs
@@ -0,0 +1,54 @@
+using Content.Client.Message;
+using Content.Shared.Cargo;
+using Content.Shared.Cargo.Prototypes;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Cargo.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class BountyHistoryEntry : BoxContainer
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ public BountyHistoryEntry(CargoBountyHistoryData bounty)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ if (!_prototype.TryIndex(bounty.Bounty, out var bountyPrototype))
+ return;
+
+ var items = new List();
+ foreach (var entry in bountyPrototype.Entries)
+ {
+ items.Add(Loc.GetString("bounty-console-manifest-entry",
+ ("amount", entry.Amount),
+ ("item", Loc.GetString(entry.Name))));
+ }
+ ManifestLabel.SetMarkup(Loc.GetString("bounty-console-manifest-label", ("item", string.Join(", ", items))));
+ RewardLabel.SetMarkup(Loc.GetString("bounty-console-reward-label", ("reward", bountyPrototype.Reward)));
+ IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id)));
+
+ var stationTime = bounty.Timestamp.ToString("hh\\:mm\\:ss");
+ if (bounty.ActorName == null)
+ {
+ StatusLabel.SetMarkup(Loc.GetString("bounty-console-history-completed-label"));
+ NoticeLabel.SetMarkup(Loc.GetString("bounty-console-history-notice-completed-label", ("time", stationTime)));
+ }
+ else
+ {
+ StatusLabel.SetMarkup(Loc.GetString("bounty-console-history-skipped-label"));
+ NoticeLabel.SetMarkup(Loc.GetString("bounty-console-history-notice-skipped-label",
+ ("id", bounty.ActorName),
+ ("time", stationTime)));
+ }
+ }
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+ }
+}
diff --git a/Content.Client/Cargo/UI/CargoBountyMenu.xaml b/Content.Client/Cargo/UI/CargoBountyMenu.xaml
index bb263ff6c4a..0f093d5f8e7 100644
--- a/Content.Client/Cargo/UI/CargoBountyMenu.xaml
+++ b/Content.Client/Cargo/UI/CargoBountyMenu.xaml
@@ -11,15 +11,26 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
index 3767b45e4be..0717aacc5e6 100644
--- a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
+++ b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
@@ -17,8 +17,11 @@ public CargoBountyMenu()
RobustXamlLoader.Load(this);
}
- public void UpdateEntries(List bounties, TimeSpan untilNextSkip)
+ public void UpdateEntries(List bounties, List history, TimeSpan untilNextSkip)
{
+ MasterTabContainer.SetTabTitle(0, Loc.GetString("bounty-console-tab-available-label"));
+ MasterTabContainer.SetTabTitle(1, Loc.GetString("bounty-console-tab-history-label"));
+
BountyEntriesContainer.Children.Clear();
foreach (var b in bounties)
{
@@ -32,5 +35,12 @@ public void UpdateEntries(List bounties, TimeSpan untilNextSkip
{
MinHeight = 10
});
+
+ BountyHistoryContainer.Children.Clear();
+ foreach (var h in history)
+ {
+ var entry = new BountyHistoryEntry(h);
+ BountyHistoryContainer.AddChild(entry);
+ }
}
}
diff --git a/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs b/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs
index a7735787cbb..f58b5450ba4 100644
--- a/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs
+++ b/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs
@@ -21,6 +21,13 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
[DataField, ViewVariables(VVAccess.ReadWrite)]
public List Bounties = new();
+ ///
+ /// A list of all the bounties that have been completed or
+ /// skipped for a station.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public List History = new();
+
///
/// Used to determine unique order IDs
///
diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
index 373e8e243ba..236b018a9da 100644
--- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
+++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
@@ -8,6 +8,7 @@
using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Prototypes;
using Content.Shared.Database;
+using Content.Shared.IdentityManagement;
using Content.Shared.NameIdentifier;
using Content.Shared.Paper;
using Content.Shared.Stacks;
@@ -16,6 +17,7 @@
using Robust.Server.Containers;
using Robust.Shared.Containers;
using Robust.Shared.Random;
+using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Cargo.Systems;
@@ -25,6 +27,7 @@ public sealed partial class CargoSystem
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSys = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
[ValidatePrototypeId]
private const string BountyNameIdentifierGroup = "Bounty";
@@ -54,7 +57,7 @@ private void OnBountyConsoleOpened(EntityUid uid, CargoBountyConsoleComponent co
return;
var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime;
- _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, untilNextSkip));
+ _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, bountyDb.History, untilNextSkip));
}
private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args)
@@ -95,13 +98,13 @@ private void OnSkipBountyMessage(EntityUid uid, CargoBountyConsoleComponent comp
return;
}
- if (!TryRemoveBounty(station, bounty.Value))
+ if (!TryRemoveBounty(station, bounty.Value, null, args.Actor))
return;
FillBountyDatabase(station);
db.NextSkipTime = _timing.CurTime + db.SkipDelay;
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
- _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip));
+ _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip));
_audio.PlayPvs(component.SkipSound, uid);
}
@@ -434,15 +437,15 @@ public bool TryAddBounty(EntityUid uid, CargoBountyPrototype bounty, StationCarg
}
[PublicAPI]
- public bool TryRemoveBounty(EntityUid uid, string dataId, StationCargoBountyDatabaseComponent? component = null)
+ public bool TryRemoveBounty(EntityUid uid, string dataId, StationCargoBountyDatabaseComponent? component = null, EntityUid? actor = null)
{
if (!TryGetBountyFromId(uid, dataId, out var data, component))
return false;
- return TryRemoveBounty(uid, data.Value, component);
+ return TryRemoveBounty(uid, data.Value, component, actor);
}
- public bool TryRemoveBounty(EntityUid uid, CargoBountyData data, StationCargoBountyDatabaseComponent? component = null)
+ public bool TryRemoveBounty(EntityUid uid, CargoBountyData data, StationCargoBountyDatabaseComponent? component = null, EntityUid? actor = null)
{
if (!Resolve(uid, ref component))
return false;
@@ -451,6 +454,15 @@ public bool TryRemoveBounty(EntityUid uid, CargoBountyData data, StationCargoBou
{
if (component.Bounties[i].Id == data.Id)
{
+ string? actorName = default;
+ if (actor != null)
+ {
+ var getIdentityEvent = new TryGetIdentityShortInfoEvent(uid, actor.Value);
+ RaiseLocalEvent(getIdentityEvent);
+ actorName = getIdentityEvent.Title;
+ }
+
+ component.History.Add(new CargoBountyHistoryData(data, _gameTiming.CurTime, actorName));
component.Bounties.RemoveAt(i);
return true;
}
@@ -492,7 +504,7 @@ public void UpdateBountyConsoles()
}
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
- _uiSystem.SetUiState((uid, ui), CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip));
+ _uiSystem.SetUiState((uid, ui), CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip));
}
}
diff --git a/Content.Shared/Cargo/CargoBountyHistoryData.cs b/Content.Shared/Cargo/CargoBountyHistoryData.cs
new file mode 100644
index 00000000000..43da42d5587
--- /dev/null
+++ b/Content.Shared/Cargo/CargoBountyHistoryData.cs
@@ -0,0 +1,46 @@
+using Robust.Shared.Serialization;
+using Content.Shared.Cargo.Prototypes;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Cargo;
+
+///
+/// A data structure for storing historical information about bounties.
+///
+[DataDefinition, NetSerializable, Serializable]
+public readonly partial record struct CargoBountyHistoryData
+{
+ ///
+ /// A unique id used to identify the bounty
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public string Id { get; init; } = string.Empty;
+
+ ///
+ /// Optional name of the actor that skipped the bounty.
+ /// Only set when the bounty has been skipped.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public string? ActorName { get; init; } = default;
+
+ ///
+ /// Time when this bounty was completed or skipped
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan Timestamp { get; init; } = TimeSpan.MinValue;
+
+ ///
+ /// The prototype containing information about the bounty.
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ [DataField(required: true)]
+ public ProtoId Bounty { get; init; } = string.Empty;
+
+ public CargoBountyHistoryData(CargoBountyData bounty, TimeSpan timestamp, string? actorName)
+ {
+ Bounty = bounty.Bounty;
+ Id = bounty.Id;
+ ActorName = actorName;
+ Timestamp = timestamp;
+ }
+}
diff --git a/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs b/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs
index bf82a08127e..8c78312be19 100644
--- a/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs
+++ b/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs
@@ -50,11 +50,13 @@ public sealed partial class CargoBountyConsoleComponent : Component
public sealed class CargoBountyConsoleState : BoundUserInterfaceState
{
public List Bounties;
+ public List History;
public TimeSpan UntilNextSkip;
- public CargoBountyConsoleState(List bounties, TimeSpan untilNextSkip)
+ public CargoBountyConsoleState(List bounties, List history, TimeSpan untilNextSkip)
{
Bounties = bounties;
+ History = history;
UntilNextSkip = untilNextSkip;
}
}
diff --git a/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl b/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl
index 4314cbf4496..4d849c5bdab 100644
--- a/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl
+++ b/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl
@@ -18,3 +18,10 @@ bounty-console-flavor-right = v1.4
bounty-manifest-header = [font size=14][bold]Official cargo bounty manifest[/bold] (ID#{$id})[/font]
bounty-manifest-list-start = Item manifest:
+
+bounty-console-tab-available-label = Available
+bounty-console-tab-history-label = History
+bounty-console-history-notice-completed-label = {$time} - Completed
+bounty-console-history-notice-skipped-label = {$time} - Skipped by {$id}
+bounty-console-history-completed-label = [color=limegreen]Completed[/color]
+bounty-console-history-skipped-label = [color=red]Skipped[/color]