]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Chill bounties + fixes (#23411)
authorNemanja <98561806+EmoGarbage404@users.noreply.github.com>
Thu, 4 Jan 2024 00:34:47 +0000 (19:34 -0500)
committerGitHub <noreply@github.com>
Thu, 4 Jan 2024 00:34:47 +0000 (17:34 -0700)
* Chill bounties + fixes

* localize

* fix arbitage

22 files changed:
Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
Content.Client/Cargo/UI/BountyEntry.xaml
Content.Client/Cargo/UI/BountyEntry.xaml.cs
Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
Content.IntegrationTests/Tests/CargoTest.cs
Content.Server/Cargo/Components/CargoBountyLabelComponent.cs
Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs
Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs
Content.Server/Cargo/Systems/CargoSystem.cs
Content.Server/NameIdentifier/NameIdentifierSystem.cs
Content.Server/Station/Commands/StationCommand.cs
Content.Shared/Cargo/CargoBountyData.cs
Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs
Content.Shared/Cargo/Prototypes/CargoBountyPrototype.cs
Resources/Locale/en-US/cargo/bounties.ftl
Resources/Locale/en-US/cargo/cargo-bounty-console.ftl
Resources/Locale/en-US/commands/toolshed-commands.ftl
Resources/Prototypes/Catalog/Bounties/bounties.yml
Resources/Prototypes/Entities/Objects/Misc/paper.yml
Resources/Prototypes/name_identifier_groups.yml
Resources/Prototypes/tags.yml

index 669f64fae4c0e15db2432f96872fe3f7d606a19e..482acb3c877a9af346604d2eff788c19809db2ac 100644 (file)
@@ -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;
 
index e570b03746a00f00d46b8ba8c6c70fac90fff36e..60446327b353f9a6f6367b99a357bc04ad6aa8b3 100644 (file)
@@ -8,14 +8,13 @@
                       HorizontalExpand="True">
             <BoxContainer Orientation="Horizontal">
                 <BoxContainer Orientation="Vertical" HorizontalExpand="True">
-                    <RichTextLabel Name="TimeLabel"/>
                     <RichTextLabel Name="RewardLabel"/>
                     <RichTextLabel Name="ManifestLabel"/>
                 </BoxContainer>
                 <Control MinWidth="10"/>
                 <BoxContainer Orientation="Vertical" MinWidth="120">
                     <Button Name="PrintButton" Text="{Loc 'bounty-console-label-button-text'}" HorizontalExpand="False" HorizontalAlignment="Right"/>
-                    <Label Name="IdLabel" HorizontalAlignment="Right" Margin="0 0 5 0"/>
+                    <RichTextLabel Name="IdLabel" HorizontalAlignment="Right" Margin="0 0 5 0"/>
                 </BoxContainer>
             </BoxContainer>
             <customControls:HSeparator Margin="5 10 5 10"/>
index 54d11108406630ebfd291774dc55c6dda2d87efa..05c5673dddf5d23e28ff21762a3d7302e024f49b 100644 (file)
@@ -27,8 +27,6 @@ public sealed partial class BountyEntry : BoxContainer
         if (!_prototype.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype))
             return;
 
-        EndTime = bounty.EndTime;
-
         var items = new List<string>();
         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"))));
-    }
 }
index 62ff31df899a568bb8d449fc379b33620305c5dd..2f1756dd18b3baf175f72aeb3a219cd96118e79a 100644 (file)
@@ -9,7 +9,7 @@ namespace Content.Client.Cargo.UI;
 [GenerateTypedNameReferences]
 public sealed partial class CargoBountyMenu : FancyWindow
 {
-    public Action<int>? OnLabelButtonPressed;
+    public Action<string>? OnLabelButtonPressed;
 
     public CargoBountyMenu()
     {
index 055a21ccfd6ca1e8d45a78c8750d356ecb004965..8bb07cfd96720ae81035b507b92e1a58db5c1510 100644 (file)
@@ -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);
index b38fe2ad5e05149a3ebf2a8a4f84985b64f4df73..8eea00e09939830690f77b7c74412bf0aef17d3e 100644 (file)
@@ -10,8 +10,8 @@ public sealed partial class CargoBountyLabelComponent : Component
     /// <summary>
     /// The ID for the bounty this label corresponds to.
     /// </summary>
-    [DataField("id"), ViewVariables(VVAccess.ReadWrite)]
-    public int Id;
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public string Id = string.Empty;
 
     /// <summary>
     /// Used to prevent recursion in calculating the price.
index 68517ea144ab38565fbbd8a30d871c30335e736b..48c58321b38716131256ce71105433ccff16d5ff 100644 (file)
@@ -11,37 +11,25 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
     /// <summary>
     /// Maximum amount of bounties a station can have.
     /// </summary>
-    [DataField("maxBounties"), ViewVariables(VVAccess.ReadWrite)]
-    public int MaxBounties = 3;
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public int MaxBounties = 5;
 
     /// <summary>
     /// A list of all the bounties currently active for a station.
     /// </summary>
-    [DataField("bounties"), ViewVariables(VVAccess.ReadWrite)]
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
     public List<CargoBountyData> Bounties = new();
 
     /// <summary>
     /// Used to determine unique order IDs
     /// </summary>
-    [DataField("totalBounties")]
+    [DataField]
     public int TotalBounties;
 
-    /// <summary>
-    /// The minimum amount of time the bounty lasts before being removed.
-    /// </summary>
-    [DataField("minBountyTime"), ViewVariables(VVAccess.ReadWrite)]
-    public float MinBountyTime = 600f;
-
-    /// <summary>
-    /// The maximum amount of time the bounty lasts before being removed.
-    /// </summary>
-    [DataField("maxBountyTime"), ViewVariables(VVAccess.ReadWrite)]
-    public float MaxBountyTime = 905f;
-
     /// <summary>
     /// A list of bounty IDs that have been checked this tick.
     /// Used to prevent multiplying bounty prices.
     /// </summary>
     [DataField]
-    public HashSet<int> CheckedBounties = new();
+    public HashSet<string> CheckedBounties = new();
 }
index f502b7f92f445e4e2d78144e9086da820cb7656d..ce15542ec534ae574d6f93b08a274599bc248aef 100644 (file)
@@ -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<NameIdentifierGroupPrototype>]
+    private const string BountyNameIdentifierGroup = "Bounty";
+
+    private EntityQuery<StackComponent> _stackQuery;
+    private EntityQuery<ContainerManagerComponent> _containerQuery;
+    private EntityQuery<CargoBountyLabelComponent> _bountyLabelQuery;
 
     private void InitializeBounty()
     {
@@ -27,6 +37,10 @@ public sealed partial class CargoSystem
         SubscribeLocalEvent<CargoBountyLabelComponent, PriceCalculationEvent>(OnGetBountyPrice);
         SubscribeLocalEvent<EntitySoldEvent>(OnSold);
         SubscribeLocalEvent<StationCargoBountyDatabaseComponent, MapInitEvent>(OnMapInit);
+
+        _stackQuery = GetEntityQuery<StackComponent>();
+        _containerQuery = GetEntityQuery<ContainerManagerComponent>();
+        _bountyLabelQuery = GetEntityQuery<CargoBountyLabelComponent>();
     }
 
     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<CargoBountyPrototype>(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<ContainerManagerComponent>();
-        var labelQuery = GetEntityQuery<CargoBountyLabelComponent>();
         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<StationCargoBountyDatabaseComponent?> entity)
+    {
+        if (!Resolve(entity, ref entity.Comp))
+            return;
+
+        entity.Comp.Bounties.Clear();
+        FillBountyDatabase(entity);
+    }
+
+    public bool IsBountyComplete(EntityUid container, EntityUid? station, out HashSet<EntityUid> 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<CargoBountyPrototype>(data.Bounty, out var proto))
+        return IsBountyComplete(container, data, out _);
+    }
+
+    public bool IsBountyComplete(EntityUid container, CargoBountyData data,  out HashSet<EntityUid> bountyEntities)
+    {
+        if (!_protoMan.TryIndex(data.Bounty, out var proto))
+        {
+            bountyEntities = new();
             return false;
+        }
 
-        return IsBountyComplete(container, proto.Entries);
+        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<CargoBountyItemEntry> entries)
     {
-        var contained = new HashSet<EntityUid>();
-        if (TryComp<ContainerManagerComponent>(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(container, entries, out _);
+    }
 
-        return IsBountyComplete(contained, entries);
+    public bool IsBountyComplete(EntityUid container, IEnumerable<CargoBountyItemEntry> entries, out HashSet<EntityUid> bountyEntities)
+    {
+        return IsBountyComplete(GetBountyEntities(container), entries, out bountyEntities);
     }
 
-    public bool IsBountyComplete(HashSet<EntityUid> entities, IEnumerable<CargoBountyItemEntry> entries)
+    public bool IsBountyComplete(HashSet<EntityUid> entities, IEnumerable<CargoBountyItemEntry> entries, out HashSet<EntityUid> 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<EntityUid> GetBountyEntities(EntityUid uid)
+    {
+        var entities = new HashSet<EntityUid>
+        {
+            uid
+        };
+        if (!TryComp<ContainerManagerComponent>(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<CargoBountyPrototype>().ToList());
+        var allBounties = _protoMan.EnumeratePrototypes<CargoBountyPrototype>().ToList();
+        var filteredBounties = new List<CargoBountyPrototype>();
+        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<StationCargoBountyDatabaseComponent>();
-        while (query.MoveNext(out var uid, out var bountyDatabase))
+        while (query.MoveNext(out var bountyDatabase))
         {
             bountyDatabase.CheckedBounties.Clear();
-            var bounties = new ValueList<CargoBountyData>(bountyDatabase.Bounties);
-            foreach (var bounty in bounties)
-            {
-                if (_timing.CurTime < bounty.EndTime)
-                    continue;
-                TryRemoveBounty(uid, bounty, bountyDatabase);
-                FillBountyDatabase(uid, bountyDatabase);
-            }
         }
     }
 }
index f1a7f62051c2d5dc2055402089bf84e6c6aa6e47..00922ad1051c1b5bcdd8dc09b4b845a6fb3db5db 100644 (file)
@@ -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;
         }
index 51cfc9791cb2a6d257d7478f14b4f059f1ae6a62..7172fec7fec3038b4553fbb377add27ff180658f 100644 (file)
@@ -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;
index 6d6e9e6c697d5cf37102e3c4e0ecb447a278f3ae..87953d518b3a1f2bb31f579e4f14ec8437bbf64a 100644 (file)
@@ -46,6 +46,15 @@ public sealed class NameIdentifierSystem : EntitySystem
         }
     }
 
+    /// <summary>
+    ///     Generates a new unique name/suffix for a given entity and adds it to <see cref="CurrentIds"/>
+    ///     but does not set the entity's name.
+    /// </summary>
+    public string GenerateUniqueName(EntityUid uid, ProtoId<NameIdentifierGroupPrototype> proto, out int randomVal)
+    {
+        return GenerateUniqueName(uid, _prototypeManager.Index(proto), out randomVal);
+    }
+
     /// <summary>
     ///     Generates a new unique name/suffix for a given entity and adds it to <see cref="CurrentIds"/>
     ///     but does not set the entity's name.
index b2381a0322a97303efaba74c6ede29e480eccf56..96e47d6336345858d8e9344e62bbd91aa1f9f4b6 100644 (file)
@@ -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<EntityUid> 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<CargoSystem>();
+
+        _cargo.RerollBountyDatabase(input);
+    }
 }
 
 public record struct OnlyOneStationsError : IConError
index 275d27ea673f21ea62121a88cf7e2ae0ce9c5f1b..3de49b57542604ed5b171a9b53a713cbb3c0c9ad 100644 (file)
@@ -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.
 /// </summary>
 [DataDefinition, NetSerializable, Serializable]
-public readonly partial record struct CargoBountyData(int Id, string Bounty, TimeSpan EndTime)
+public readonly partial record struct CargoBountyData
 {
     /// <summary>
-    /// A numeric id used to identify the bounty
+    /// A unique id used to identify the bounty
     /// </summary>
-    [DataField("id"), ViewVariables(VVAccess.ReadWrite)]
-    public int Id { get; init; } = Id;
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public string Id { get; init; } = string.Empty;
 
     /// <summary>
     /// The prototype containing information about the bounty.
     /// </summary>
     [ViewVariables(VVAccess.ReadWrite)]
-    [DataField("bounty", customTypeSerializer: typeof(PrototypeIdSerializer<CargoBountyPrototype>), required:true)]
-    public string Bounty { get; init; } = Bounty;
+    [DataField(required: true)]
+    public ProtoId<CargoBountyPrototype> Bounty { get; init; } = string.Empty;
 
-    /// <summary>
-    /// The time at which the bounty is closed and no longer is available.
-    /// </summary>
-    [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}";
     }
 }
index 6ea07acc9d52a2dbafb7e986c4b52d23645f7433..7b1acf836fdcd3fdf41303a6de2b0d9feb9b1857 100644 (file)
@@ -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;
     }
index 89c1d153acb096dcdc710666b60e6406a7ad83af..bf4907b0dd4b9b838afdee934ab624282c5f04a1 100644 (file)
@@ -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.
 /// </summary>
-[Prototype("cargoBounty"), Serializable, NetSerializable]
+[Prototype, Serializable, NetSerializable]
 public sealed partial class CargoBountyPrototype : IPrototype
 {
     /// <inheritdoc/>
@@ -19,20 +19,26 @@ public sealed partial class CargoBountyPrototype : IPrototype
     /// <summary>
     /// The monetary reward for completing the bounty
     /// </summary>
-    [DataField("reward", required: true)]
+    [DataField(required: true)]
     public int Reward;
 
     /// <summary>
     /// A description for flava purposes.
     /// </summary>
-    [DataField("description")]
-    public string Description = string.Empty;
+    [DataField]
+    public LocId Description = string.Empty;
 
     /// <summary>
     /// The entries that must be satisfied for the cargo bounty to be complete.
     /// </summary>
-    [DataField("entries", required: true)]
+    [DataField( required: true)]
     public List<CargoBountyItemEntry> Entries = new();
+
+    /// <summary>
+    /// A prefix appended to the beginning of a bounty's ID.
+    /// </summary>
+    [DataField]
+    public string IdPrefix = "NT";
 }
 
 [DataDefinition, Serializable, NetSerializable]
@@ -41,7 +47,7 @@ public readonly partial record struct CargoBountyItemEntry()
     /// <summary>
     /// A whitelist for determining what items satisfy the entry.
     /// </summary>
-    [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()
     /// <summary>
     /// How much of the item must be present to satisfy the entry
     /// </summary>
-    [DataField("amount")]
+    [DataField]
     public int Amount { get; init; } = 1;
 
     /// <summary>
     /// A player-facing name for the item.
     /// </summary>
-    [DataField("name")]
-    public string Name { get; init; } = string.Empty;
+    [DataField]
+    public LocId Name { get; init; } = string.Empty;
 }
index 7008718c131249532e8b8c001c893e9b2f1ff916..a6722c2c30e6dbe40511f00ade91e211e90f963a 100644 (file)
@@ -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.
index 54ff46f2b61fd54325c287a3c10dcbec54ff6280..ec80d91f47f34d1399d70e18f0439305b6a115b5 100644 (file)
@@ -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:
index 3747712aa275d625e3fe895e081aa5e5c38c4150..04f6aa08faca01129368493024b0d31f13775d88 100644 (file)
@@ -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 =
index 1e2fd783db852fb081120faf9f4707a0b12539a3..ca65db98ac4ed50651a39f4beb86b34b0c5875c5 100644 (file)
@@ -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
 
 - type: cargoBounty
   id: BountyCorn
-  reward: 20000
+  reward: 10000
   description: bounty-description-corn
   entries:
   - name: bounty-item-corn
 
 - type: cargoBounty
   id: BountyCrayon
-  reward: 8000
+  reward: 4000
   description: bounty-description-crayon
   entries:
   - name: bounty-item-crayon
 
 - type: cargoBounty
   id: BountyCubanCarp
-  reward: 32000
+  reward: 16000
   description: bounty-description-cuban-carp
+  idPrefix: CC
   entries:
   - name: bounty-item-cuban-carp
     amount: 1
 
 - type: cargoBounty
   id: BountyDonkPocket
-  reward: 12000
+  reward: 6000
   description: bounty-description-donk-pocket
+  idPrefix: CC
   entries:
   - name: bounty-item-donk-pocket
     amount: 12
 
 - type: cargoBounty
   id: BountyDonut
-  reward: 12000
+  reward: 6000
   description: bounty-description-donut
+  idPrefix: CC
   entries:
   - name: bounty-item-donut
     amount: 10
 
 - type: cargoBounty
   id: BountyFigurine
-  reward: 16000
+  reward: 8000
   description: bounty-description-figurine
   entries:
   - name: bounty-item-figurine
       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
 
 - type: cargoBounty
   id: BountyGalaxyThistle
-  reward: 24000
+  reward: 12000
   description: bounty-description-galaxythistle
   entries:
   - name: bounty-item-galaxythistle
 
 - type: cargoBounty
   id: BountyHandcuffs
-  reward: 4000
+  reward: 1000
   description: bounty-description-handcuffs
+  idPrefix: CC
   entries:
   - name: bounty-item-handcuffs
     amount: 5
 
 - type: cargoBounty
   id: BountyKnife
-  reward: 12000
+  reward: 6000
   description: bounty-description-knife
   entries:
   - name: bounty-item-knife
 
 - type: cargoBounty
   id: BountyLemon
-  reward: 20000
+  reward: 10000
   description: bounty-description-lemon
   entries:
   - name: bounty-item-lemon
 
 - type: cargoBounty
   id: BountyLime
-  reward: 20000
+  reward: 10000
   description: bounty-description-lime
+  idPrefix: CC
   entries:
   - name: bounty-item-lime
     amount: 7
 
 - type: cargoBounty
   id: BountyLung
-  reward: 35000
+  reward: 2000
   description: bounty-description-lung
   entries:
   - name: bounty-item-lung
 
 - type: cargoBounty
   id: BountyMonkeyCube
-  reward: 8000
+  reward: 2000
   description: bounty-description-monkey-cube
+  idPrefix: CC
   entries:
   - name: bounty-item-monkey-cube
     amount: 3
   id: BountyMouse
   reward: 2400
   description: bounty-description-mouse
+  idPrefix: SS13-
   entries:
   - name: bounty-item-mouse
     amount: 5
 
 - type: cargoBounty
   id: BountyPancake
-  reward: 20000
+  reward: 10000
   description: bounty-description-pancake
   entries:
   - name: bounty-item-pancake
   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
 
 - type: cargoBounty
   id: BountyPie
-  reward: 31415
+  reward: 3141
   description: bounty-description-pie
   entries:
   - name: bounty-item-pie
   id: BountyPrisonUniform
   reward: 8000
   description: bounty-description-prison-uniform
+  idPrefix: TG
   entries:
   - name: bounty-item-prison-uniform
     amount: 4
 
 - type: cargoBounty
   id: BountyRadio
-  reward: 10000
+  reward: 7500
   description: bounty-description-radio
   entries:
   - name: bounty-item-radio
       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
 
 - type: cargoBounty
   id: BountySoap
-  reward: 8000
+  reward: 4000
   description: bounty-description-soap
   entries:
   - name: bounty-item-soap
 
 - type: cargoBounty
   id: BountySoup
-  reward: 12000
+  reward: 6000
   description: bounty-description-soup
   entries:
   - name: bounty-item-soup
 
 - type: cargoBounty
   id: BountySyringe
-  reward: 10000
+  reward: 5000
   description: bounty-description-syringe
   entries:
   - name: bounty-item-syringe
   id: BountyTechDisk
   reward: 25000
   description: bounty-description-tech-disk
+  idPrefix: SS13-
   entries:
   - name: bounty-item-tech-disk
     amount: 5
   id: BountyToolbox
   reward: 8000
   description: bounty-description-toolbox
+  idPrefix: CC
   entries:
   - name: bounty-item-toolbox
     amount: 6
 
 - type: cargoBounty
   id: BountyTrash
-  reward: 700
+  reward: 500
   description: bounty-description-trash
   entries:
   - name: bounty-item-trash
 
 - type: cargoBounty
   id: BountyOrgans
-  reward: 3500
+  reward: 2000
   description: bounty-description-organs
+  idPrefix: UNTH
   entries:
   - name: bounty-item-organs
     amount: 8
     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
   id: BountyLaserGun
   reward: 28500
   description: bounty-description-lasergun
+  idPrefix: IV
   entries:
   - name: bounty-lasergun
     amount: 6
 
 - 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
index 5bfc31dbce078dcbea4b0aaf4025c950b0fc9e3e..e66829c7d47168abef31db8d007f5329a0a7be22 100644 (file)
   - type: Tag
     tags:
     - Write
+    - Pen
   - type: Sprite
     sprite: Objects/Misc/bureaucracy.rsi
     state: pen
       types:
         Blunt: 10
   - type: StealTarget
-    stealGroup: BoxFolderQmClipboard
\ No newline at end of file
+    stealGroup: BoxFolderQmClipboard
index e6dce1fc556cde1b6ac687ecb7247da837272996..39cda032f9c1807414481bb971f4bf9f4d364e8e 100644 (file)
@@ -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
index 44e8d9593f6b3823650031cb13e6cbe70f9bf232..df6bf7cd24e80905bc8618ee26cf897424496bec 100644 (file)
 - type: Tag
   id: Pancake
 
+- type: Tag
+  id: Pen
+
 - type: Tag
   id: PepperShaker