diff --git a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
index 669f64fae4..482acb3c87 100644
--- a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
+++ b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
@@ -1,7 +1,6 @@
using Content.Client.Cargo.UI;
using Content.Shared.Cargo.Components;
using JetBrains.Annotations;
-using Robust.Client.GameObjects;
namespace Content.Client.Cargo.BUI;
diff --git a/Content.Client/Cargo/UI/BountyEntry.xaml b/Content.Client/Cargo/UI/BountyEntry.xaml
index e570b03746..60446327b3 100644
--- a/Content.Client/Cargo/UI/BountyEntry.xaml
+++ b/Content.Client/Cargo/UI/BountyEntry.xaml
@@ -8,14 +8,13 @@
HorizontalExpand="True">
-
-
+
diff --git a/Content.Client/Cargo/UI/BountyEntry.xaml.cs b/Content.Client/Cargo/UI/BountyEntry.xaml.cs
index 54d1110840..05c5673ddd 100644
--- a/Content.Client/Cargo/UI/BountyEntry.xaml.cs
+++ b/Content.Client/Cargo/UI/BountyEntry.xaml.cs
@@ -27,8 +27,6 @@ public sealed partial class BountyEntry : BoxContainer
if (!_prototype.TryIndex(bounty.Bounty, out var bountyPrototype))
return;
- EndTime = bounty.EndTime;
-
var items = new List();
foreach (var entry in bountyPrototype.Entries)
{
@@ -39,16 +37,8 @@ public sealed partial class BountyEntry : BoxContainer
ManifestLabel.SetMarkup(Loc.GetString("bounty-console-manifest-label", ("item", string.Join(", ", items))));
RewardLabel.SetMarkup(Loc.GetString("bounty-console-reward-label", ("reward", bountyPrototype.Reward)));
DescriptionLabel.SetMarkup(Loc.GetString("bounty-console-description-label", ("description", Loc.GetString(bountyPrototype.Description))));
- IdLabel.Text = Loc.GetString("bounty-console-id-label", ("id", bounty.Id));
+ IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id)));
PrintButton.OnPressed += _ => OnButtonPressed?.Invoke();
}
-
- protected override void FrameUpdate(FrameEventArgs args)
- {
- base.FrameUpdate(args);
-
- var remaining = TimeSpan.FromSeconds(Math.Max((EndTime - _timing.CurTime).TotalSeconds, 0));
- TimeLabel.SetMarkup(Loc.GetString("bounty-console-time-label", ("time", remaining.ToString("mm':'ss"))));
- }
}
diff --git a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
index 62ff31df89..2f1756dd18 100644
--- a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
+++ b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
@@ -9,7 +9,7 @@ namespace Content.Client.Cargo.UI;
[GenerateTypedNameReferences]
public sealed partial class CargoBountyMenu : FancyWindow
{
- public Action? OnLabelButtonPressed;
+ public Action? OnLabelButtonPressed;
public CargoBountyMenu()
{
diff --git a/Content.IntegrationTests/Tests/CargoTest.cs b/Content.IntegrationTests/Tests/CargoTest.cs
index 055a21ccfd..8bb07cfd96 100644
--- a/Content.IntegrationTests/Tests/CargoTest.cs
+++ b/Content.IntegrationTests/Tests/CargoTest.cs
@@ -80,7 +80,7 @@ public sealed class CargoTest
foreach (var bounty in bounties)
{
if (cargo.IsBountyComplete(ent, bounty))
- Assert.That(proto.PointCost, Is.GreaterThan(bounty.Reward), $"Found arbitrage on {bounty.ID} cargo bounty! Product {proto.ID} costs {proto.PointCost} but fulfills bounty {bounty.ID} with reward {bounty.Reward}!");
+ Assert.That(proto.PointCost, Is.GreaterThanOrEqualTo(bounty.Reward), $"Found arbitrage on {bounty.ID} cargo bounty! Product {proto.ID} costs {proto.PointCost} but fulfills bounty {bounty.ID} with reward {bounty.Reward}!");
}
entManager.DeleteEntity(ent);
diff --git a/Content.Server/Cargo/Components/CargoBountyLabelComponent.cs b/Content.Server/Cargo/Components/CargoBountyLabelComponent.cs
index b38fe2ad5e..8eea00e099 100644
--- a/Content.Server/Cargo/Components/CargoBountyLabelComponent.cs
+++ b/Content.Server/Cargo/Components/CargoBountyLabelComponent.cs
@@ -10,8 +10,8 @@ public sealed partial class CargoBountyLabelComponent : Component
///
/// The ID for the bounty this label corresponds to.
///
- [DataField("id"), ViewVariables(VVAccess.ReadWrite)]
- public int Id;
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public string Id = string.Empty;
///
/// Used to prevent recursion in calculating the price.
diff --git a/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs b/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs
index 68517ea144..48c58321b3 100644
--- a/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs
+++ b/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs
@@ -11,37 +11,25 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
///
/// Maximum amount of bounties a station can have.
///
- [DataField("maxBounties"), ViewVariables(VVAccess.ReadWrite)]
- public int MaxBounties = 3;
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public int MaxBounties = 5;
///
/// A list of all the bounties currently active for a station.
///
- [DataField("bounties"), ViewVariables(VVAccess.ReadWrite)]
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
public List Bounties = new();
///
/// Used to determine unique order IDs
///
- [DataField("totalBounties")]
+ [DataField]
public int TotalBounties;
- ///
- /// The minimum amount of time the bounty lasts before being removed.
- ///
- [DataField("minBountyTime"), ViewVariables(VVAccess.ReadWrite)]
- public float MinBountyTime = 600f;
-
- ///
- /// The maximum amount of time the bounty lasts before being removed.
- ///
- [DataField("maxBountyTime"), ViewVariables(VVAccess.ReadWrite)]
- public float MaxBountyTime = 905f;
-
///
/// A list of bounty IDs that have been checked this tick.
/// Used to prevent multiplying bounty prices.
///
[DataField]
- public HashSet CheckedBounties = new();
+ public HashSet CheckedBounties = new();
}
diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
index f502b7f92f..ce15542ec5 100644
--- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
+++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
@@ -2,14 +2,16 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Cargo.Components;
using Content.Server.Labels;
+using Content.Server.NameIdentifier;
using Content.Server.Paper;
using Content.Shared.Cargo;
using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Prototypes;
using Content.Shared.Database;
+using Content.Shared.NameIdentifier;
+using Content.Shared.Stacks;
using JetBrains.Annotations;
using Robust.Server.Containers;
-using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Random;
using Robust.Shared.Utility;
@@ -19,6 +21,14 @@ namespace Content.Server.Cargo.Systems;
public sealed partial class CargoSystem
{
[Dependency] private readonly ContainerSystem _container = default!;
+ [Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!;
+
+ [ValidatePrototypeId]
+ private const string BountyNameIdentifierGroup = "Bounty";
+
+ private EntityQuery _stackQuery;
+ private EntityQuery _containerQuery;
+ private EntityQuery _bountyLabelQuery;
private void InitializeBounty()
{
@@ -27,6 +37,10 @@ public sealed partial class CargoSystem
SubscribeLocalEvent(OnGetBountyPrice);
SubscribeLocalEvent(OnSold);
SubscribeLocalEvent(OnMapInit);
+
+ _stackQuery = GetEntityQuery();
+ _containerQuery = GetEntityQuery();
+ _bountyLabelQuery = GetEntityQuery();
}
private void OnBountyConsoleOpened(EntityUid uid, CargoBountyConsoleComponent component, BoundUIOpenedEvent args)
@@ -98,7 +112,7 @@ public sealed partial class CargoSystem
if (!TryGetBountyFromId(station, component.Id, out var bounty, database))
return;
- if (!_protoMan.TryIndex(bounty.Value.Bounty, out var bountyPrototype) ||
+ if (!_protoMan.TryIndex(bounty.Value.Bounty, out var bountyPrototype) ||
!IsBountyComplete(container.Owner, bountyPrototype))
return;
@@ -112,25 +126,15 @@ public sealed partial class CargoSystem
private void OnSold(ref EntitySoldEvent args)
{
- var containerQuery = GetEntityQuery();
- var labelQuery = GetEntityQuery();
foreach (var sold in args.Sold)
{
- if (!containerQuery.TryGetComponent(sold, out var containerMan))
- continue;
-
- // make sure this label was actually applied to a crate.
- if (!_container.TryGetContainer(sold, LabelSystem.ContainerName, out var container, containerMan))
- continue;
-
- if (container.ContainedEntities.FirstOrNull() is not { } label ||
- !labelQuery.TryGetComponent(label, out var component))
+ if (!TryGetBountyLabel(sold, out _, out var component))
continue;
if (!TryGetBountyFromId(args.Station, component.Id, out var bounty))
continue;
- if (!IsBountyComplete(container.Owner, bounty.Value))
+ if (!IsBountyComplete(sold, bounty.Value))
continue;
TryRemoveBounty(args.Station, bounty.Value);
@@ -139,6 +143,28 @@ public sealed partial class CargoSystem
}
}
+ private bool TryGetBountyLabel(EntityUid uid,
+ [NotNullWhen(true)] out EntityUid? labelEnt,
+ [NotNullWhen(true)] out CargoBountyLabelComponent? labelComp)
+ {
+ labelEnt = null;
+ labelComp = null;
+ if (!_containerQuery.TryGetComponent(uid, out var containerMan))
+ return false;
+
+ // make sure this label was actually applied to a crate.
+ if (!_container.TryGetContainer(uid, LabelSystem.ContainerName, out var container, containerMan))
+ return false;
+
+ if (container.ContainedEntities.FirstOrNull() is not { } label ||
+ !_bountyLabelQuery.TryGetComponent(label, out var component))
+ return false;
+
+ labelEnt = label;
+ labelComp = component;
+ return true;
+ }
+
private void OnMapInit(EntityUid uid, StationCargoBountyDatabaseComponent component, MapInitEvent args)
{
FillBountyDatabase(uid, component);
@@ -161,12 +187,53 @@ public sealed partial class CargoSystem
UpdateBountyConsoles();
}
+ public void RerollBountyDatabase(Entity entity)
+ {
+ if (!Resolve(entity, ref entity.Comp))
+ return;
+
+ entity.Comp.Bounties.Clear();
+ FillBountyDatabase(entity);
+ }
+
+ public bool IsBountyComplete(EntityUid container, EntityUid? station, out HashSet bountyEntities)
+ {
+ if (!TryGetBountyLabel(container, out _, out var component))
+ {
+ bountyEntities = new();
+ return false;
+ }
+
+ station ??= _station.GetOwningStation(container);
+ if (station == null)
+ {
+ bountyEntities = new();
+ return false;
+ }
+
+ if (!TryGetBountyFromId(station.Value, component.Id, out var bounty))
+ {
+ bountyEntities = new();
+ return false;
+ }
+
+ return IsBountyComplete(container, bounty.Value, out bountyEntities);
+ }
+
public bool IsBountyComplete(EntityUid container, CargoBountyData data)
{
- if (!_protoMan.TryIndex(data.Bounty, out var proto))
- return false;
+ return IsBountyComplete(container, data, out _);
+ }
- return IsBountyComplete(container, proto.Entries);
+ public bool IsBountyComplete(EntityUid container, CargoBountyData data, out HashSet bountyEntities)
+ {
+ if (!_protoMan.TryIndex(data.Bounty, out var proto))
+ {
+ bountyEntities = new();
+ return false;
+ }
+
+ return IsBountyComplete(container, proto.Entries, out bountyEntities);
}
public bool IsBountyComplete(EntityUid container, string id)
@@ -184,26 +251,18 @@ public sealed partial class CargoSystem
public bool IsBountyComplete(EntityUid container, IEnumerable entries)
{
- var contained = new HashSet();
- if (TryComp(container, out var containers))
- {
- foreach (var con in containers.Containers.Values)
- {
- if (con.ID == LabelSystem.ContainerName)
- continue;
-
- foreach (var ent in con.ContainedEntities)
- {
- contained.Add(ent);
- }
- }
- }
-
- return IsBountyComplete(contained, entries);
+ return IsBountyComplete(container, entries, out _);
}
- public bool IsBountyComplete(HashSet entities, IEnumerable entries)
+ public bool IsBountyComplete(EntityUid container, IEnumerable entries, out HashSet bountyEntities)
{
+ return IsBountyComplete(GetBountyEntities(container), entries, out bountyEntities);
+ }
+
+ public bool IsBountyComplete(HashSet entities, IEnumerable entries, out HashSet bountyEntities)
+ {
+ bountyEntities = new();
+
foreach (var entry in entries)
{
var count = 0;
@@ -215,7 +274,8 @@ public sealed partial class CargoSystem
{
if (!entry.Whitelist.IsValid(entity, EntityManager))
continue;
- count++;
+
+ count += _stackQuery.CompOrNull(entity)?.Count ?? 1;
temp.Add(entity);
if (count >= entry.Amount)
@@ -228,17 +288,58 @@ public sealed partial class CargoSystem
foreach (var ent in temp)
{
entities.Remove(ent);
+ bountyEntities.Add(ent);
}
}
return true;
}
+ private HashSet GetBountyEntities(EntityUid uid)
+ {
+ var entities = new HashSet
+ {
+ uid
+ };
+ if (!TryComp(uid, out var containers))
+ return entities;
+
+ foreach (var container in containers.Containers.Values)
+ {
+ foreach (var ent in container.ContainedEntities)
+ {
+ if (_bountyLabelQuery.HasComponent(ent))
+ continue;
+
+ var children = GetBountyEntities(ent);
+ foreach (var child in children)
+ {
+ entities.Add(child);
+ }
+ }
+ }
+
+ return entities;
+ }
+
[PublicAPI]
public bool TryAddBounty(EntityUid uid, StationCargoBountyDatabaseComponent? component = null)
{
+ if (!Resolve(uid, ref component))
+ return false;
+
// todo: consider making the cargo bounties weighted.
- var bounty = _random.Pick(_protoMan.EnumeratePrototypes().ToList());
+ var allBounties = _protoMan.EnumeratePrototypes().ToList();
+ var filteredBounties = new List();
+ foreach (var proto in allBounties)
+ {
+ if (component.Bounties.Any(b => b.Bounty == proto.ID))
+ continue;
+ filteredBounties.Add(proto);
+ }
+
+ var pool = filteredBounties.Count == 0 ? allBounties : filteredBounties;
+ var bounty = _random.Pick(pool);
return TryAddBounty(uid, bounty, component);
}
@@ -261,17 +362,15 @@ public sealed partial class CargoSystem
if (component.Bounties.Count >= component.MaxBounties)
return false;
- var duration = MathF.Round(_random.NextFloat(component.MinBountyTime, component.MaxBountyTime) / 15) * 15;
- var endTime = _timing.CurTime + TimeSpan.FromSeconds(duration);
-
- component.Bounties.Add(new CargoBountyData(component.TotalBounties, bounty.ID, endTime));
+ _nameIdentifier.GenerateUniqueName(uid, BountyNameIdentifierGroup, out var randomVal);
+ component.Bounties.Add(new CargoBountyData(bounty, randomVal));
_adminLogger.Add(LogType.Action, LogImpact.Low, $"Added bounty \"{bounty.ID}\" (id:{component.TotalBounties}) to station {ToPrettyString(uid)}");
component.TotalBounties++;
return true;
}
[PublicAPI]
- public bool TryRemoveBounty(EntityUid uid, int dataId, StationCargoBountyDatabaseComponent? component = null)
+ public bool TryRemoveBounty(EntityUid uid, string dataId, StationCargoBountyDatabaseComponent? component = null)
{
if (!TryGetBountyFromId(uid, dataId, out var data, component))
return false;
@@ -298,7 +397,7 @@ public sealed partial class CargoSystem
public bool TryGetBountyFromId(
EntityUid uid,
- int id,
+ string id,
[NotNullWhen(true)] out CargoBountyData? bounty,
StationCargoBountyDatabaseComponent? component = null)
{
@@ -333,17 +432,9 @@ public sealed partial class CargoSystem
private void UpdateBounty()
{
var query = EntityQueryEnumerator();
- while (query.MoveNext(out var uid, out var bountyDatabase))
+ while (query.MoveNext(out var bountyDatabase))
{
bountyDatabase.CheckedBounties.Clear();
- var bounties = new ValueList(bountyDatabase.Bounties);
- foreach (var bounty in bounties)
- {
- if (_timing.CurTime < bounty.EndTime)
- continue;
- TryRemoveBounty(uid, bounty, bountyDatabase);
- FillBountyDatabase(uid, bountyDatabase);
- }
}
}
}
diff --git a/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs b/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs
index f1a7f62051..00922ad105 100644
--- a/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs
+++ b/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs
@@ -288,10 +288,15 @@ public sealed partial class CargoSystem
return false;
}
+ var complete = IsBountyComplete(uid, (EntityUid?) null, out var bountyEntities);
+
// Recursively check for mobs at any point.
var children = xform.ChildEnumerator;
while (children.MoveNext(out var child))
{
+ if (complete && bountyEntities.Contains(child))
+ continue;
+
if (!CanSell(child, _xformQuery.GetComponent(child)))
return false;
}
diff --git a/Content.Server/Cargo/Systems/CargoSystem.cs b/Content.Server/Cargo/Systems/CargoSystem.cs
index 51cfc9791c..7172fec7fe 100644
--- a/Content.Server/Cargo/Systems/CargoSystem.cs
+++ b/Content.Server/Cargo/Systems/CargoSystem.cs
@@ -13,7 +13,6 @@ using Content.Shared.Containers.ItemSlots;
using Content.Shared.Mobs.Components;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
diff --git a/Content.Server/NameIdentifier/NameIdentifierSystem.cs b/Content.Server/NameIdentifier/NameIdentifierSystem.cs
index 6d6e9e6c69..87953d518b 100644
--- a/Content.Server/NameIdentifier/NameIdentifierSystem.cs
+++ b/Content.Server/NameIdentifier/NameIdentifierSystem.cs
@@ -46,6 +46,15 @@ public sealed class NameIdentifierSystem : EntitySystem
}
}
+ ///
+ /// Generates a new unique name/suffix for a given entity and adds it to
+ /// but does not set the entity's name.
+ ///
+ public string GenerateUniqueName(EntityUid uid, ProtoId proto, out int randomVal)
+ {
+ return GenerateUniqueName(uid, _prototypeManager.Index(proto), out randomVal);
+ }
+
///
/// Generates a new unique name/suffix for a given entity and adds it to
/// but does not set the entity's name.
diff --git a/Content.Server/Station/Commands/StationCommand.cs b/Content.Server/Station/Commands/StationCommand.cs
index b2381a0322..96e47d6336 100644
--- a/Content.Server/Station/Commands/StationCommand.cs
+++ b/Content.Server/Station/Commands/StationCommand.cs
@@ -1,6 +1,7 @@
using System.Diagnostics;
using System.Linq;
using Content.Server.Administration;
+using Content.Server.Cargo.Systems;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Shared.Administration;
@@ -15,6 +16,7 @@ namespace Content.Server.Station.Commands;
public sealed class StationsCommand : ToolshedCommand
{
private StationSystem? _station;
+ private CargoSystem? _cargo;
[CommandImplementation("list")]
public IEnumerable List()
@@ -111,6 +113,15 @@ public sealed class StationsCommand : ToolshedCommand
_station.RenameStation(input, name.Evaluate(ctx)!);
}
+
+ [CommandImplementation("rerollBounties")]
+ public void RerollBounties([CommandInvocationContext] IInvocationContext ctx,
+ [PipedArgument] EntityUid input)
+ {
+ _cargo ??= GetSys();
+
+ _cargo.RerollBountyDatabase(input);
+ }
}
public record struct OnlyOneStationsError : IConError
diff --git a/Content.Shared/Cargo/CargoBountyData.cs b/Content.Shared/Cargo/CargoBountyData.cs
index 275d27ea67..3de49b5754 100644
--- a/Content.Shared/Cargo/CargoBountyData.cs
+++ b/Content.Shared/Cargo/CargoBountyData.cs
@@ -1,7 +1,6 @@
using Robust.Shared.Serialization;
using Content.Shared.Cargo.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Prototypes;
namespace Content.Shared.Cargo;
@@ -9,28 +8,24 @@ namespace Content.Shared.Cargo;
/// A data structure for storing currently available bounties.
///
[DataDefinition, NetSerializable, Serializable]
-public readonly partial record struct CargoBountyData(int Id, string Bounty, TimeSpan EndTime)
+public readonly partial record struct CargoBountyData
{
///
- /// A numeric id used to identify the bounty
+ /// A unique id used to identify the bounty
///
- [DataField("id"), ViewVariables(VVAccess.ReadWrite)]
- public int Id { get; init; } = Id;
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public string Id { get; init; } = string.Empty;
///
/// The prototype containing information about the bounty.
///
[ViewVariables(VVAccess.ReadWrite)]
- [DataField("bounty", customTypeSerializer: typeof(PrototypeIdSerializer), required:true)]
- public string Bounty { get; init; } = Bounty;
+ [DataField(required: true)]
+ public ProtoId Bounty { get; init; } = string.Empty;
- ///
- /// The time at which the bounty is closed and no longer is available.
- ///
- [DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
- public TimeSpan EndTime { get; init; } = EndTime;
-
- public CargoBountyData() : this(default, string.Empty, default)
+ public CargoBountyData(CargoBountyPrototype bounty, int uniqueIdentifier)
{
+ Bounty = bounty.ID;
+ Id = $"{bounty.IdPrefix}{uniqueIdentifier:D3}";
}
}
diff --git a/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs b/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs
index 6ea07acc9d..7b1acf836f 100644
--- a/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs
+++ b/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs
@@ -48,9 +48,9 @@ public sealed class CargoBountyConsoleState : BoundUserInterfaceState
[Serializable, NetSerializable]
public sealed class BountyPrintLabelMessage : BoundUserInterfaceMessage
{
- public int BountyId;
+ public string BountyId;
- public BountyPrintLabelMessage(int bountyId)
+ public BountyPrintLabelMessage(string bountyId)
{
BountyId = bountyId;
}
diff --git a/Content.Shared/Cargo/Prototypes/CargoBountyPrototype.cs b/Content.Shared/Cargo/Prototypes/CargoBountyPrototype.cs
index 89c1d153ac..bf4907b0dd 100644
--- a/Content.Shared/Cargo/Prototypes/CargoBountyPrototype.cs
+++ b/Content.Shared/Cargo/Prototypes/CargoBountyPrototype.cs
@@ -9,7 +9,7 @@ namespace Content.Shared.Cargo.Prototypes;
/// that must be sold together in a labeled container in order
/// to receive a monetary reward.
///
-[Prototype("cargoBounty"), Serializable, NetSerializable]
+[Prototype, Serializable, NetSerializable]
public sealed partial class CargoBountyPrototype : IPrototype
{
///
@@ -19,20 +19,26 @@ public sealed partial class CargoBountyPrototype : IPrototype
///
/// The monetary reward for completing the bounty
///
- [DataField("reward", required: true)]
+ [DataField(required: true)]
public int Reward;
///
/// A description for flava purposes.
///
- [DataField("description")]
- public string Description = string.Empty;
+ [DataField]
+ public LocId Description = string.Empty;
///
/// The entries that must be satisfied for the cargo bounty to be complete.
///
- [DataField("entries", required: true)]
+ [DataField( required: true)]
public List Entries = new();
+
+ ///
+ /// A prefix appended to the beginning of a bounty's ID.
+ ///
+ [DataField]
+ public string IdPrefix = "NT";
}
[DataDefinition, Serializable, NetSerializable]
@@ -41,7 +47,7 @@ public readonly partial record struct CargoBountyItemEntry()
///
/// A whitelist for determining what items satisfy the entry.
///
- [DataField("whitelist", required: true)]
+ [DataField(required: true)]
public EntityWhitelist Whitelist { get; init; } = default!;
// todo: implement some kind of simple generic condition system
@@ -49,12 +55,12 @@ public readonly partial record struct CargoBountyItemEntry()
///
/// How much of the item must be present to satisfy the entry
///
- [DataField("amount")]
+ [DataField]
public int Amount { get; init; } = 1;
///
/// A player-facing name for the item.
///
- [DataField("name")]
- public string Name { get; init; } = string.Empty;
+ [DataField]
+ public LocId Name { get; init; } = string.Empty;
}
diff --git a/Resources/Locale/en-US/cargo/bounties.ftl b/Resources/Locale/en-US/cargo/bounties.ftl
index 7008718c13..a6722c2c30 100644
--- a/Resources/Locale/en-US/cargo/bounties.ftl
+++ b/Resources/Locale/en-US/cargo/bounties.ftl
@@ -31,7 +31,7 @@ bounty-item-pen = Pen
bounty-item-percussion = Percussion instrument
bounty-item-pie = Pie
bounty-item-prison-uniform = Prison uniform
-bounty-item-radio = Radio
+bounty-item-radio = Radio or Headset
bounty-item-research-disk = Research disk
bounty-item-shiv = Shiv
bounty-item-soap = Soap
@@ -96,9 +96,9 @@ bounty-description-trash = Recently a group of janitors have run out of trash to
bounty-description-anomaly-core = Suddenly we've run out of anomaly cores, including inert nuclei. Send us any anomaly cores so that we can continue to observe their final decay process.
bounty-description-borg-module = Scientists at the neighboring station have only studied Borg production, not module production. They are stuck in the research process and need references to work from. Send any Borg modules to inspire your colleagues.
bounty-description-artifact-fragment = Scientists at a nearby station are requesting artifact fragments for microxenoarchaeology studies. Ordinary artifacts are too large for their micro research platforms. Send some of the artifact fragments your sector is rich in.
-bounty-description-organs = Arachnid settlement orders a large supply of organs. The official reason is "a thorough study of the similarities and differences of the humanoid races."
+bounty-description-organs = Arachnid settlement orders a large supply of organs. The official reason is "a thorough study of the similarities and differences of the humanoid races."
bounty-description-labeler = Due to a bureaucratic error, our sorting center almost sent hundreds of crates of carrots to the Unathi settlement. We urgently need additional labelers to restore order in the warehouse as a matter of urgency.
bounty-description-warm-cloth = The Unath construction crew freezes and is unable to restore power to their station. They need to be sent a set of any clothing to protect them from the cold.
bounty-description-battery = As the Arachnid settlement prepares for a solar flare, they are requesting a large shipment of power batteries. We're sending out a request for delivery.
-bounty-description-lasergun = The Salvage Caravan requests a large shipment of laser weapons to mop up a hive of xenomorphs.
-bounty-description-food = After the rat king invasion, a neighboring unathi station was left completely without food. A large meat food shipment is needed.
\ No newline at end of file
+bounty-description-lasergun = The Salvage Caravan requests a large shipment of laser weapons to mop up a hive of xenomorphs.
+bounty-description-food = After the rat king invasion, a neighboring unathi station was left completely without food. A large meat food shipment is needed.
diff --git a/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl b/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl
index 54ff46f2b6..ec80d91f47 100644
--- a/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl
+++ b/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl
@@ -2,7 +2,7 @@
bounty-console-label-button-text = Print label
bounty-console-time-label = Time: [color=orange]{$time}[/color]
bounty-console-reward-label = Reward: [color=limegreen]${$reward}[/color]
-bounty-console-manifest-label = Manifest: [color=gray]{$item}[/color]
+bounty-console-manifest-label = Manifest: [color=orange]{$item}[/color]
bounty-console-manifest-entry =
{ $amount ->
[1] {$item}
@@ -14,5 +14,5 @@ bounty-console-id-label = ID#{$id}
bounty-console-flavor-left = Bounties sourced from local unscrupulous dealers.
bounty-console-flavor-right = v1.4
-bounty-manifest-header = Official cargo bounty manifest (ID#{$id})
+bounty-manifest-header = [font size=14][bold]Official cargo bounty manifest[/bold] (ID#{$id})[/font]
bounty-manifest-list-start = Item manifest:
diff --git a/Resources/Locale/en-US/commands/toolshed-commands.ftl b/Resources/Locale/en-US/commands/toolshed-commands.ftl
index 3747712aa2..04f6aa08fa 100644
--- a/Resources/Locale/en-US/commands/toolshed-commands.ftl
+++ b/Resources/Locale/en-US/commands/toolshed-commands.ftl
@@ -40,6 +40,8 @@ command-description-stations-rename =
Renames the given station.
command-description-stations-largestgrid =
Returns the largest grid the given station has, if any.
+command-description-stations-rerollBounties =
+ Clears all the current bounties for the station and gets a new selection.
command-description-stationevent-lsprob =
Lists the probability of different station events occuring out of the entire pool.
command-description-stationevent-lsprobtime =
diff --git a/Resources/Prototypes/Catalog/Bounties/bounties.yml b/Resources/Prototypes/Catalog/Bounties/bounties.yml
index 1e2fd783db..ca65db98ac 100644
--- a/Resources/Prototypes/Catalog/Bounties/bounties.yml
+++ b/Resources/Prototypes/Catalog/Bounties/bounties.yml
@@ -11,8 +11,9 @@
- type: cargoBounty
id: BountyBaseballBat
- reward: 8000
+ reward: 4000
description: bounty-description-baseball-bat
+ idPrefix: CC
entries:
- name: bounty-item-baseball-bat
amount: 5
@@ -22,7 +23,7 @@
- type: cargoBounty
id: BountyBoxHug
- reward: 6000
+ reward: 3000
description: bounty-description-box-hugs
entries:
- name: bounty-item-box-hugs
@@ -33,7 +34,7 @@
- type: cargoBounty
id: BountyBrain
- reward: 25000
+ reward: 12500
description: bounty-description-brain
entries:
- name: bounty-item-brain
@@ -44,7 +45,7 @@
- type: cargoBounty
id: BountyBread
- reward: 4000
+ reward: 2000
description: bounty-description-bread
entries:
- name: bounty-item-bread
@@ -55,8 +56,9 @@
- type: cargoBounty
id: BountyBriefcase
- reward: 15000
+ reward: 7500
description: bounty-description-briefcase
+ idPrefix: CC
entries:
- name: bounty-item-briefcase
amount: 5
@@ -66,8 +68,9 @@
- type: cargoBounty
id: BountyCarrot
- reward: 15000
+ reward: 7500
description: bounty-description-carrot
+ idPrefix: SS15-
entries:
- name: bounty-item-carrot
amount: 10
@@ -77,7 +80,7 @@
- type: cargoBounty
id: BountyCarrotFries
- reward: 14000
+ reward: 7000
description: bounty-description-carrot-fries
entries:
- name: bounty-item-carrot-fries
@@ -88,8 +91,9 @@
- type: cargoBounty
id: BountyCarp
- reward: 25000
+ reward: 12500
description: bounty-description-carp
+ idPrefix: CC
entries:
- name: bounty-item-carp
amount: 1
@@ -115,7 +119,7 @@
- type: cargoBounty
id: BountyCorn
- reward: 20000
+ reward: 10000
description: bounty-description-corn
entries:
- name: bounty-item-corn
@@ -126,7 +130,7 @@
- type: cargoBounty
id: BountyCrayon
- reward: 8000
+ reward: 4000
description: bounty-description-crayon
entries:
- name: bounty-item-crayon
@@ -137,8 +141,9 @@
- type: cargoBounty
id: BountyCubanCarp
- reward: 32000
+ reward: 16000
description: bounty-description-cuban-carp
+ idPrefix: CC
entries:
- name: bounty-item-cuban-carp
amount: 1
@@ -148,8 +153,9 @@
- type: cargoBounty
id: BountyDonkPocket
- reward: 12000
+ reward: 6000
description: bounty-description-donk-pocket
+ idPrefix: CC
entries:
- name: bounty-item-donk-pocket
amount: 12
@@ -159,8 +165,9 @@
- type: cargoBounty
id: BountyDonut
- reward: 12000
+ reward: 6000
description: bounty-description-donut
+ idPrefix: CC
entries:
- name: bounty-item-donut
amount: 10
@@ -170,7 +177,7 @@
- type: cargoBounty
id: BountyFigurine
- reward: 16000
+ reward: 8000
description: bounty-description-figurine
entries:
- name: bounty-item-figurine
@@ -179,21 +186,11 @@
tags:
- Figurine
-- type: cargoBounty
- id: BountyFleshMonster
- reward: 30000
- description: bounty-description-flesh-monster
- entries:
- - name: bounty-item-flesh-monster
- amount: 3
- whitelist:
- tags:
- - Flesh
-
- type: cargoBounty
id: BountyFlower
- reward: 4000
+ reward: 2000
description: bounty-description-flower
+ idPrefix: CC
entries:
- name: bounty-item-flower
amount: 3
@@ -203,7 +200,7 @@
- type: cargoBounty
id: BountyGalaxyThistle
- reward: 24000
+ reward: 12000
description: bounty-description-galaxythistle
entries:
- name: bounty-item-galaxythistle
@@ -214,8 +211,9 @@
- type: cargoBounty
id: BountyHandcuffs
- reward: 4000
+ reward: 1000
description: bounty-description-handcuffs
+ idPrefix: CC
entries:
- name: bounty-item-handcuffs
amount: 5
@@ -236,7 +234,7 @@
- type: cargoBounty
id: BountyKnife
- reward: 12000
+ reward: 6000
description: bounty-description-knife
entries:
- name: bounty-item-knife
@@ -247,7 +245,7 @@
- type: cargoBounty
id: BountyLemon
- reward: 20000
+ reward: 10000
description: bounty-description-lemon
entries:
- name: bounty-item-lemon
@@ -258,8 +256,9 @@
- type: cargoBounty
id: BountyLime
- reward: 20000
+ reward: 10000
description: bounty-description-lime
+ idPrefix: CC
entries:
- name: bounty-item-lime
amount: 7
@@ -269,7 +268,7 @@
- type: cargoBounty
id: BountyLung
- reward: 35000
+ reward: 2000
description: bounty-description-lung
entries:
- name: bounty-item-lung
@@ -280,8 +279,9 @@
- type: cargoBounty
id: BountyMonkeyCube
- reward: 8000
+ reward: 2000
description: bounty-description-monkey-cube
+ idPrefix: CC
entries:
- name: bounty-item-monkey-cube
amount: 3
@@ -293,6 +293,7 @@
id: BountyMouse
reward: 2400
description: bounty-description-mouse
+ idPrefix: SS13-
entries:
- name: bounty-item-mouse
amount: 5
@@ -302,7 +303,7 @@
- type: cargoBounty
id: BountyPancake
- reward: 20000
+ reward: 10000
description: bounty-description-pancake
entries:
- name: bounty-item-pancake
@@ -317,14 +318,14 @@
description: bounty-description-pen
entries:
- name: bounty-item-pen
- amount: 10
+ amount: 5
whitelist:
tags:
- - Write
+ - Pen
- type: cargoBounty
id: BountyPercussion
- reward: 25000
+ reward: 15000
description: bounty-description-percussion
entries:
- name: bounty-item-percussion
@@ -335,7 +336,7 @@
- type: cargoBounty
id: BountyPie
- reward: 31415
+ reward: 3141
description: bounty-description-pie
entries:
- name: bounty-item-pie
@@ -348,6 +349,7 @@
id: BountyPrisonUniform
reward: 8000
description: bounty-description-prison-uniform
+ idPrefix: TG
entries:
- name: bounty-item-prison-uniform
amount: 4
@@ -357,7 +359,7 @@
- type: cargoBounty
id: BountyRadio
- reward: 10000
+ reward: 7500
description: bounty-description-radio
entries:
- name: bounty-item-radio
@@ -369,21 +371,11 @@
tags:
- Radio
-- type: cargoBounty
- id: BountyResearchDisk
- reward: 12000
- description: bounty-description-research-disk
- entries:
- - name: bounty-item-research-disk
- amount: 1
- whitelist:
- components:
- - ResearchDisk
-
- type: cargoBounty
id: BountyShiv
- reward: 8000
+ reward: 4000
description: bounty-description-shiv
+ idPrefix: SYN
entries:
- name: bounty-item-shiv
amount: 5
@@ -393,7 +385,7 @@
- type: cargoBounty
id: BountySoap
- reward: 8000
+ reward: 4000
description: bounty-description-soap
entries:
- name: bounty-item-soap
@@ -404,7 +396,7 @@
- type: cargoBounty
id: BountySoup
- reward: 12000
+ reward: 6000
description: bounty-description-soup
entries:
- name: bounty-item-soup
@@ -426,7 +418,7 @@
- type: cargoBounty
id: BountySyringe
- reward: 10000
+ reward: 5000
description: bounty-description-syringe
entries:
- name: bounty-item-syringe
@@ -439,6 +431,7 @@
id: BountyTechDisk
reward: 25000
description: bounty-description-tech-disk
+ idPrefix: SS13-
entries:
- name: bounty-item-tech-disk
amount: 5
@@ -450,6 +443,7 @@
id: BountyToolbox
reward: 8000
description: bounty-description-toolbox
+ idPrefix: CC
entries:
- name: bounty-item-toolbox
amount: 6
@@ -459,7 +453,7 @@
- type: cargoBounty
id: BountyTrash
- reward: 700
+ reward: 500
description: bounty-description-trash
entries:
- name: bounty-item-trash
@@ -503,8 +497,9 @@
- type: cargoBounty
id: BountyOrgans
- reward: 3500
+ reward: 2000
description: bounty-description-organs
+ idPrefix: UNTH
entries:
- name: bounty-item-organs
amount: 8
@@ -522,22 +517,24 @@
whitelist:
components:
- HandLabeler
-
+
- type: cargoBounty
id: BountyWarmCloth
- reward: 6000
+ reward: 2000
description: bounty-description-warm-cloth
+ idPrefix: UNTH
entries:
- name: bounty-item-warm-cloth
amount: 8
whitelist:
components:
- TemperatureProtection
-
+
- type: cargoBounty
id: BountyBattery
- reward: 24500
+ reward: 15000
description: bounty-description-battery
+ idPrefix: UNTH
entries:
- name: bounty-item-battery
amount: 15
@@ -549,6 +546,7 @@
id: BountyLaserGun
reward: 28500
description: bounty-description-lasergun
+ idPrefix: IV
entries:
- name: bounty-lasergun
amount: 6
@@ -558,11 +556,12 @@
- type: cargoBounty
id: BountyFood
- reward: 9500
+ reward: 4000
description: bounty-description-food
+ idPrefix: UNTH
entries:
- name: bounty-food
amount: 30
whitelist:
tags:
- - Meat
\ No newline at end of file
+ - Meat
diff --git a/Resources/Prototypes/Entities/Objects/Misc/paper.yml b/Resources/Prototypes/Entities/Objects/Misc/paper.yml
index 5bfc31dbce..e66829c7d4 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/paper.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/paper.yml
@@ -239,6 +239,7 @@
- type: Tag
tags:
- Write
+ - Pen
- type: Sprite
sprite: Objects/Misc/bureaucracy.rsi
state: pen
@@ -647,4 +648,4 @@
types:
Blunt: 10
- type: StealTarget
- stealGroup: BoxFolderQmClipboard
\ No newline at end of file
+ stealGroup: BoxFolderQmClipboard
diff --git a/Resources/Prototypes/name_identifier_groups.yml b/Resources/Prototypes/name_identifier_groups.yml
index e6dce1fc55..39cda032f9 100644
--- a/Resources/Prototypes/name_identifier_groups.yml
+++ b/Resources/Prototypes/name_identifier_groups.yml
@@ -35,3 +35,8 @@
# Used to suffix a number to any mob to identify player controlled mob griefing.
- type: nameIdentifierGroup
id: GenericNumber
+
+- type: nameIdentifierGroup
+ id: Bounty
+ minValue: 0
+ maxValue: 999
diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml
index 44e8d9593f..df6bf7cd24 100644
--- a/Resources/Prototypes/tags.yml
+++ b/Resources/Prototypes/tags.yml
@@ -830,6 +830,9 @@
- type: Tag
id: Pancake
+- type: Tag
+ id: Pen
+
- type: Tag
id: PepperShaker