diff --git a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
index 482acb3c87..d3365702bc 100644
--- a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
+++ b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
@@ -27,6 +27,11 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
SendMessage(new BountyPrintLabelMessage(id));
};
+ _menu.OnSkipButtonPressed += id =>
+ {
+ SendMessage(new BountySkipMessage(id));
+ };
+
_menu.OpenCentered();
}
@@ -37,7 +42,7 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
if (message is not CargoBountyConsoleState state)
return;
- _menu?.UpdateEntries(state.Bounties);
+ _menu?.UpdateEntries(state.Bounties, state.UntilNextSkip);
}
protected override void Dispose(bool disposing)
diff --git a/Content.Client/Cargo/UI/BountyEntry.xaml b/Content.Client/Cargo/UI/BountyEntry.xaml
index 60446327b3..7c61323bd5 100644
--- a/Content.Client/Cargo/UI/BountyEntry.xaml
+++ b/Content.Client/Cargo/UI/BountyEntry.xaml
@@ -13,7 +13,18 @@
-
+
+
+
+
diff --git a/Content.Client/Cargo/UI/BountyEntry.xaml.cs b/Content.Client/Cargo/UI/BountyEntry.xaml.cs
index 1fc8a4986a..027d7b3e80 100644
--- a/Content.Client/Cargo/UI/BountyEntry.xaml.cs
+++ b/Content.Client/Cargo/UI/BountyEntry.xaml.cs
@@ -1,11 +1,13 @@
using Content.Client.Message;
using Content.Shared.Cargo;
using Content.Shared.Cargo.Prototypes;
+using Content.Shared.Random;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
+using Serilog;
namespace Content.Client.Cargo.UI;
@@ -14,15 +16,19 @@ public sealed partial class BountyEntry : BoxContainer
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
- public Action? OnButtonPressed;
+ public Action? OnLabelButtonPressed;
+ public Action? OnSkipButtonPressed;
public TimeSpan EndTime;
+ public TimeSpan UntilNextSkip;
- public BountyEntry(CargoBountyData bounty)
+ public BountyEntry(CargoBountyData bounty, TimeSpan untilNextSkip)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
+ UntilNextSkip = untilNextSkip;
+
if (!_prototype.TryIndex(bounty.Bounty, out var bountyPrototype))
return;
@@ -38,6 +44,27 @@ public sealed partial class BountyEntry : BoxContainer
DescriptionLabel.SetMarkup(Loc.GetString("bounty-console-description-label", ("description", Loc.GetString(bountyPrototype.Description))));
IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id)));
- PrintButton.OnPressed += _ => OnButtonPressed?.Invoke();
+ PrintButton.OnPressed += _ => OnLabelButtonPressed?.Invoke();
+ SkipButton.OnPressed += _ => OnSkipButtonPressed?.Invoke();
+ }
+
+ private void UpdateSkipButton(float deltaSeconds)
+ {
+ UntilNextSkip -= TimeSpan.FromSeconds(deltaSeconds);
+ if (UntilNextSkip > TimeSpan.Zero)
+ {
+ SkipButton.Label.Text = UntilNextSkip.ToString("mm\\:ss");
+ SkipButton.Disabled = true;
+ return;
+ }
+
+ SkipButton.Label.Text = Loc.GetString("bounty-console-skip-button-text");
+ SkipButton.Disabled = false;
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+ UpdateSkipButton(args.DeltaSeconds);
}
}
diff --git a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
index 2f1756dd18..3767b45e4b 100644
--- a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
+++ b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
@@ -10,19 +10,21 @@ namespace Content.Client.Cargo.UI;
public sealed partial class CargoBountyMenu : FancyWindow
{
public Action? OnLabelButtonPressed;
+ public Action? OnSkipButtonPressed;
public CargoBountyMenu()
{
RobustXamlLoader.Load(this);
}
- public void UpdateEntries(List bounties)
+ public void UpdateEntries(List bounties, TimeSpan untilNextSkip)
{
BountyEntriesContainer.Children.Clear();
foreach (var b in bounties)
{
- var entry = new BountyEntry(b);
- entry.OnButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
+ var entry = new BountyEntry(b, untilNextSkip);
+ entry.OnLabelButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
+ entry.OnSkipButtonPressed += () => OnSkipButtonPressed?.Invoke(b.Id);
BountyEntriesContainer.AddChild(entry);
}
diff --git a/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs b/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs
index d26794a632..a7735787cb 100644
--- a/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs
+++ b/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs
@@ -1,4 +1,5 @@
using Content.Shared.Cargo;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Cargo.Components;
@@ -32,4 +33,16 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
///
[DataField]
public HashSet CheckedBounties = new();
+
+ ///
+ /// The time at which players will be able to skip the next bounty.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ public TimeSpan NextSkipTime = TimeSpan.Zero;
+
+ ///
+ /// The time between skipping bounties.
+ ///
+ [DataField]
+ public TimeSpan SkipDelay = TimeSpan.FromMinutes(15);
}
diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
index ee5ae631fd..22e5c67e17 100644
--- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
+++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
@@ -4,7 +4,7 @@ using Content.Server.Cargo.Components;
using Content.Server.Labels;
using Content.Server.NameIdentifier;
using Content.Server.Paper;
-using Content.Server.Station.Systems;
+using Content.Shared.Access.Components;
using Content.Shared.Cargo;
using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Prototypes;
@@ -35,6 +35,7 @@ public sealed partial class CargoSystem
{
SubscribeLocalEvent(OnBountyConsoleOpened);
SubscribeLocalEvent(OnPrintLabelMessage);
+ SubscribeLocalEvent(OnSkipBountyMessage);
SubscribeLocalEvent(OnGetBountyPrice);
SubscribeLocalEvent(OnSold);
SubscribeLocalEvent(OnMapInit);
@@ -50,7 +51,8 @@ public sealed partial class CargoSystem
!TryComp(station, out var bountyDb))
return;
- _uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties));
+ var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime;
+ _uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, untilNextSkip));
}
private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args)
@@ -70,6 +72,37 @@ public sealed partial class CargoSystem
_audio.PlayPvs(component.PrintSound, uid);
}
+ private void OnSkipBountyMessage(EntityUid uid, CargoBountyConsoleComponent component, BountySkipMessage args)
+ {
+ if (_station.GetOwningStation(uid) is not { } station || !TryComp(station, out var db))
+ return;
+
+ if (_timing.CurTime < db.NextSkipTime)
+ return;
+
+ if (!TryGetBountyFromId(station, args.BountyId, out var bounty))
+ return;
+
+ if (args.Session.AttachedEntity is not { Valid: true } mob)
+ return;
+
+ if (TryComp(uid, out var accessReaderComponent) &&
+ !_accessReaderSystem.IsAllowed(mob, uid, accessReaderComponent))
+ {
+ _audio.PlayPvs(component.DenySound, uid);
+ return;
+ }
+
+ if (!TryRemoveBounty(station, bounty.Value))
+ return;
+
+ FillBountyDatabase(station);
+ db.NextSkipTime = _timing.CurTime + db.SkipDelay;
+ var untilNextSkip = db.NextSkipTime - _timing.CurTime;
+ _uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip));
+ _audio.PlayPvs(component.SkipSound, uid);
+ }
+
public void SetupBountyLabel(EntityUid uid, EntityUid stationId, CargoBountyData bounty, PaperComponent? paper = null, CargoBountyLabelComponent? label = null)
{
if (!Resolve(uid, ref paper, ref label) || !_protoMan.TryIndex(bounty.Bounty, out var prototype))
@@ -431,7 +464,8 @@ public sealed partial class CargoSystem
!TryComp(station, out var db))
continue;
- _uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties), ui: ui);
+ var untilNextSkip = db.NextSkipTime - _timing.CurTime;
+ _uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip), ui: ui);
}
}
diff --git a/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs b/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs
index 7b1acf836f..bf82a08127 100644
--- a/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs
+++ b/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs
@@ -32,16 +32,30 @@ public sealed partial class CargoBountyConsoleComponent : Component
///
[DataField("printSound")]
public SoundSpecifier PrintSound = new SoundPathSpecifier("/Audio/Machines/printer.ogg");
+
+ ///
+ /// The sound made when the bounty is skipped.
+ ///
+ [DataField("skipSound")]
+ public SoundSpecifier SkipSound = new SoundPathSpecifier("/Audio/Effects/Cargo/ping.ogg");
+
+ ///
+ /// The sound made when bounty skipping is denied due to lacking access.
+ ///
+ [DataField("denySound")]
+ public SoundSpecifier DenySound = new SoundPathSpecifier("/Audio/Effects/Cargo/buzz_two.ogg");
}
[NetSerializable, Serializable]
public sealed class CargoBountyConsoleState : BoundUserInterfaceState
{
public List Bounties;
+ public TimeSpan UntilNextSkip;
- public CargoBountyConsoleState(List bounties)
+ public CargoBountyConsoleState(List bounties, TimeSpan untilNextSkip)
{
Bounties = bounties;
+ UntilNextSkip = untilNextSkip;
}
}
@@ -55,3 +69,14 @@ public sealed class BountyPrintLabelMessage : BoundUserInterfaceMessage
BountyId = bountyId;
}
}
+
+[Serializable, NetSerializable]
+public sealed class BountySkipMessage : BoundUserInterfaceMessage
+{
+ public string BountyId;
+
+ public BountySkipMessage(string bountyId)
+ {
+ BountyId = bountyId;
+ }
+}
diff --git a/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl b/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl
index ec80d91f47..bf7868e3df 100644
--- a/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl
+++ b/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl
@@ -1,5 +1,6 @@
bounty-console-menu-title = Cargo bounty console
bounty-console-label-button-text = Print label
+bounty-console-skip-button-text = Skip
bounty-console-time-label = Time: [color=orange]{$time}[/color]
bounty-console-reward-label = Reward: [color=limegreen]${$reward}[/color]
bounty-console-manifest-label = Manifest: [color=orange]{$item}[/color]
diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
index d467a01f83..95bf2e1dd4 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
@@ -791,6 +791,8 @@
radius: 1.5
energy: 1.6
color: "#b89f25"
+ - type: AccessReader
+ access: [["Quartermaster"]]
- type: GuideHelp
guides:
- CargoBounties