Add history tab to bounty console (#33932)
* 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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
22
Content.Client/Cargo/UI/BountyHistoryEntry.xaml
Normal file
22
Content.Client/Cargo/UI/BountyHistoryEntry.xaml
Normal file
@@ -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>
|
||||
49
Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs
Normal file
49
Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs
Normal file
@@ -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 ?? "")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,15 +11,28 @@
|
||||
<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">
|
||||
|
||||
@@ -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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
67
Content.Shared/Cargo/CargoBountyHistoryData.cs
Normal file
67
Content.Shared/Cargo/CargoBountyHistoryData.cs
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user