]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add history tab to bounty console (#33932)
authorBarryNorfolk <barrynorfolkman@protonmail.com>
Thu, 30 Jan 2025 17:27:36 +0000 (18:27 +0100)
committerGitHub <noreply@github.com>
Thu, 30 Jan 2025 17:27:36 +0000 (09:27 -0800)
* Add struct for holding historical data on cargo bounties

* Add localisation strings for bounty history

* Add new XAML entry for display bounty history

* Expand cargo bounty menu to include tabs

* Ensure station databases hold historical bounty data

* Add to the bounty history when removing one from active

* Feed bounty history into cargo's bounty system

* Move tab title setting to constructor

* Remove redundant access specifications

* Remove un-needed override

* Fixup BountyHistoryEntry backing code

* Fix formatting in CargoBountyMenu

* Reformat BountyHistoryData

* Rework TryRemoveBounty to use new Entity type

* Add Enum for showing bounty results

* Rework look and feel of History tab

* Add visible text when no bounties have been completed yet

* Remove control

* Swap default to null

* Reverse ordering of bounties so last entry comes first

* Remove redundant Visible

* Move enum docs into the enum

Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
Content.Client/Cargo/UI/BountyHistoryEntry.xaml [new file with mode: 0644]
Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs [new file with mode: 0644]
Content.Client/Cargo/UI/CargoBountyMenu.xaml
Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs
Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
Content.Shared/Cargo/CargoBountyHistoryData.cs [new file with mode: 0644]
Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs
Resources/Locale/en-US/cargo/cargo-bounty-console.ftl

index 44c40143d830a2fb236c1773f435542783f41344..04075000f5b9d486dff1c8e27c9a1c7b008a747f 100644 (file)
@@ -39,6 +39,6 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
         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 (file)
index 0000000..905cf02
--- /dev/null
@@ -0,0 +1,22 @@
+<BoxContainer xmlns="https://spacestation14.io"
+                xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
+                Margin="10 10 10 0"
+                HorizontalExpand="True">
+    <PanelContainer StyleClasses="AngleRect" HorizontalExpand="True">
+        <BoxContainer Orientation="Vertical"
+                      HorizontalExpand="True">
+            <BoxContainer Orientation="Horizontal">
+                <BoxContainer Orientation="Vertical" HorizontalExpand="True">
+                    <RichTextLabel Name="RewardLabel"/>
+                    <RichTextLabel Name="ManifestLabel"/>
+                </BoxContainer>
+                <BoxContainer Orientation="Vertical" MinWidth="120" Margin="0 0 10 0">
+                    <RichTextLabel Name="TimestampLabel" HorizontalAlignment="Right" />
+                    <RichTextLabel Name="IdLabel" HorizontalAlignment="Right" />
+                </BoxContainer>
+            </BoxContainer>
+            <customControls:HSeparator Margin="5 10 5 10"/>
+            <RichTextLabel Name="NoticeLabel" />
+        </BoxContainer>
+    </PanelContainer>
+</BoxContainer>
diff --git a/Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs
new file mode 100644 (file)
index 0000000..54804be
--- /dev/null
@@ -0,0 +1,49 @@
+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<string>();
+        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)));
+
+        TimestampLabel.SetMarkup(bounty.Timestamp.ToString(@"hh\:mm\:ss"));
+
+        if (bounty.Result == CargoBountyHistoryData.BountyResult.Completed)
+        {
+            NoticeLabel.SetMarkup(Loc.GetString("bounty-console-history-notice-completed-label"));
+        }
+        else
+        {
+            NoticeLabel.SetMarkup(Loc.GetString("bounty-console-history-notice-skipped-label",
+                ("id", bounty.ActorName ?? "")));
+        }
+    }
+}
index bb263ff6c4ab7fc7bde772e4ba7cdb79f4132cd6..526ba69129bb408eaa10077531fd67078303b71d 100644 (file)
             <PanelContainer.PanelOverride>
                 <gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
             </PanelContainer.PanelOverride>
-            <ScrollContainer HScrollEnabled="False"
-                             HorizontalExpand="True"
-                             VerticalExpand="True">
-                <BoxContainer Name="BountyEntriesContainer"
-                              Orientation="Vertical"
-                              VerticalExpand="True"
-                              HorizontalExpand="True">
-                </BoxContainer>
-            </ScrollContainer>
+            <TabContainer Name="MasterTabContainer" VerticalExpand="True" HorizontalExpand="True">
+                <ScrollContainer HScrollEnabled="False"
+                                 HorizontalExpand="True"
+                                 VerticalExpand="True">
+                    <BoxContainer Name="BountyEntriesContainer"
+                                  Orientation="Vertical"
+                                  VerticalExpand="True"
+                                  HorizontalExpand="True" />
+                </ScrollContainer>
+                <ScrollContainer HScrollEnabled="False"
+                                 HorizontalExpand="True"
+                                 VerticalExpand="True">
+                    <Label Name="NoHistoryLabel"
+                           Text="{Loc 'bounty-console-history-empty-label'}"
+                           Visible="False"
+                           Align="Center" />
+                    <BoxContainer Name="BountyHistoryContainer"
+                                  Orientation="Vertical"
+                                  VerticalExpand="True"
+                                  HorizontalExpand="True" />
+                </ScrollContainer>
+            </TabContainer>
         </PanelContainer>
         <!-- Footer -->
         <BoxContainer Orientation="Vertical">
index 3767b45e4bed43c5baf3fe9afe96c264156890e8..c289fb6ed83cc3100f5e5b928c55da4afaf42a65 100644 (file)
@@ -15,9 +15,12 @@ public sealed partial class CargoBountyMenu : FancyWindow
     public CargoBountyMenu()
     {
         RobustXamlLoader.Load(this);
+
+        MasterTabContainer.SetTabTitle(0, Loc.GetString("bounty-console-tab-available-label"));
+        MasterTabContainer.SetTabTitle(1, Loc.GetString("bounty-console-tab-history-label"));
     }
 
-    public void UpdateEntries(List<CargoBountyData> bounties, TimeSpan untilNextSkip)
+    public void UpdateEntries(List<CargoBountyData> bounties, List<CargoBountyHistoryData> history, TimeSpan untilNextSkip)
     {
         BountyEntriesContainer.Children.Clear();
         foreach (var b in bounties)
@@ -32,5 +35,21 @@ public sealed partial class CargoBountyMenu : FancyWindow
         {
             MinHeight = 10
         });
+
+        BountyHistoryContainer.Children.Clear();
+        if (history.Count == 0)
+        {
+            NoHistoryLabel.Visible = true;
+        }
+        else
+        {
+            NoHistoryLabel.Visible = false;
+
+            // Show the history in reverse, so last entry is first in the list
+            for (var i = history.Count - 1; i >= 0; i--)
+            {
+                BountyHistoryContainer.AddChild(new BountyHistoryEntry(history[i]));
+            }
+        }
     }
 }
index a7735787cbb69257b3282e4a049b38672833e7ac..c650438b2861c6d8e38502f96b381f15b8f7da55 100644 (file)
@@ -12,15 +12,22 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
     /// <summary>
     /// Maximum amount of bounties a station can have.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public int MaxBounties = 6;
 
     /// <summary>
     /// A list of all the bounties currently active for a station.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public List<CargoBountyData> Bounties = new();
 
+    /// <summary>
+    /// A list of all the bounties that have been completed or
+    /// skipped for a station.
+    /// </summary>
+    [DataField]
+    public List<CargoBountyHistoryData> History = new();
+
     /// <summary>
     /// Used to determine unique order IDs
     /// </summary>
index 373e8e243ba974d70ec3977e3d68d1a3fb5d64a2..7f74fe269d4c39337f05ccb98eea86a95104424b 100644 (file)
@@ -8,6 +8,7 @@ using Content.Shared.Cargo;
 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 JetBrains.Annotations;
 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<NameIdentifierGroupPrototype>]
     private const string BountyNameIdentifierGroup = "Bounty";
@@ -54,7 +57,7 @@ public sealed partial class CargoSystem
             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 @@ public sealed partial class CargoSystem
             return;
         }
 
-        if (!TryRemoveBounty(station, bounty.Value))
+        if (!TryRemoveBounty(station, bounty.Value, true, 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);
     }
 
@@ -179,7 +182,7 @@ public sealed partial class CargoSystem
                 continue;
             }
 
-            TryRemoveBounty(station, bounty.Value);
+            TryRemoveBounty(station, bounty.Value, false);
             FillBountyDatabase(station);
             _adminLogger.Add(LogType.Action, LogImpact.Low, $"Bounty \"{bounty.Value.Bounty}\" (id:{bounty.Value.Id}) was fulfilled");
         }
@@ -434,24 +437,44 @@ public sealed partial class CargoSystem
     }
 
     [PublicAPI]
-    public bool TryRemoveBounty(EntityUid uid, string dataId, StationCargoBountyDatabaseComponent? component = null)
+    public bool TryRemoveBounty(Entity<StationCargoBountyDatabaseComponent?> ent,
+        string dataId,
+        bool skipped,
+        EntityUid? actor = null)
     {
-        if (!TryGetBountyFromId(uid, dataId, out var data, component))
+        if (!TryGetBountyFromId(ent.Owner, dataId, out var data, ent.Comp))
             return false;
 
-        return TryRemoveBounty(uid, data.Value, component);
+        return TryRemoveBounty(ent, data.Value, skipped, actor);
     }
 
-    public bool TryRemoveBounty(EntityUid uid, CargoBountyData data, StationCargoBountyDatabaseComponent? component = null)
+    public bool TryRemoveBounty(Entity<StationCargoBountyDatabaseComponent?> ent,
+        CargoBountyData data,
+        bool skipped,
+        EntityUid? actor = null)
     {
-        if (!Resolve(uid, ref component))
+        if (!Resolve(ent, ref ent.Comp))
             return false;
 
-        for (var i = 0; i < component.Bounties.Count; i++)
+        for (var i = 0; i < ent.Comp.Bounties.Count; i++)
         {
-            if (component.Bounties[i].Id == data.Id)
+            if (ent.Comp.Bounties[i].Id == data.Id)
             {
-                component.Bounties.RemoveAt(i);
+                string? actorName = null;
+                if (actor != null)
+                {
+                    var getIdentityEvent = new TryGetIdentityShortInfoEvent(ent.Owner, actor.Value);
+                    RaiseLocalEvent(getIdentityEvent);
+                    actorName = getIdentityEvent.Title;
+                }
+
+                ent.Comp.History.Add(new CargoBountyHistoryData(data,
+                    skipped
+                        ? CargoBountyHistoryData.BountyResult.Skipped
+                        : CargoBountyHistoryData.BountyResult.Completed,
+                    _gameTiming.CurTime,
+                    actorName));
+                ent.Comp.Bounties.RemoveAt(i);
                 return true;
             }
         }
@@ -492,7 +515,7 @@ public sealed partial class CargoSystem
             }
 
             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 (file)
index 0000000..eb46bc1
--- /dev/null
@@ -0,0 +1,67 @@
+using Content.Shared.Cargo.Prototypes;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Cargo;
+
+/// <summary>
+/// A data structure for storing historical information about bounties.
+/// </summary>
+[DataDefinition, NetSerializable, Serializable]
+public readonly partial record struct CargoBountyHistoryData
+{
+    /// <summary>
+    /// A unique id used to identify the bounty
+    /// </summary>
+    [DataField]
+    public string Id { get; init; } = string.Empty;
+
+    /// <summary>
+    /// Whether this bounty was completed or skipped.
+    /// </summary>
+    [DataField]
+    public BountyResult Result { get; init; } = BountyResult.Completed;
+
+    /// <summary>
+    /// Optional name of the actor that completed/skipped the bounty.
+    /// </summary>
+    [DataField]
+    public string? ActorName { get; init; } = default;
+
+    /// <summary>
+    /// Time when this bounty was completed or skipped
+    /// </summary>
+    [DataField]
+    public TimeSpan Timestamp { get; init; } = TimeSpan.MinValue;
+
+    /// <summary>
+    /// The prototype containing information about the bounty.
+    /// </summary>
+    [DataField(required: true)]
+    public ProtoId<CargoBountyPrototype> Bounty { get; init; } = string.Empty;
+
+    public CargoBountyHistoryData(CargoBountyData bounty, BountyResult result, TimeSpan timestamp, string? actorName)
+    {
+        Bounty = bounty.Bounty;
+        Result = result;
+        Id = bounty.Id;
+        ActorName = actorName;
+        Timestamp = timestamp;
+    }
+
+    /// <summary>
+    /// Covers how a bounty was actually finished.
+    /// </summary>
+    public enum BountyResult
+    {
+        /// <summary>
+        /// Bounty was actually fulfilled and the goods sold
+        /// </summary>
+        Completed = 0,
+
+        /// <summary>
+        /// Bounty was explicitly skipped by some actor
+        /// </summary>
+        Skipped = 1,
+    }
+}
index bf82a08127e530994bd116461b76bb4a7c0387d4..8c78312be19b858889c0caf335dd2d20630d8417 100644 (file)
@@ -50,11 +50,13 @@ public sealed partial class CargoBountyConsoleComponent : Component
 public sealed class CargoBountyConsoleState : BoundUserInterfaceState
 {
     public List<CargoBountyData> Bounties;
+    public List<CargoBountyHistoryData> History;
     public TimeSpan UntilNextSkip;
 
-    public CargoBountyConsoleState(List<CargoBountyData> bounties, TimeSpan untilNextSkip)
+    public CargoBountyConsoleState(List<CargoBountyData> bounties, List<CargoBountyHistoryData> history, TimeSpan untilNextSkip)
     {
         Bounties = bounties;
+        History = history;
         UntilNextSkip = untilNextSkip;
     }
 }
index 4314cbf44960583cd9fac7f361c04af4dc713065..d7a7025927fcb1b2ecec9a108c6778906ae5c99b 100644 (file)
@@ -18,3 +18,9 @@ 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-empty-label = No bounty history found
+bounty-console-history-notice-completed-label = [color=limegreen]Completed[/color]
+bounty-console-history-notice-skipped-label = [color=red]Skipped[/color] by {$id}