Chill bounties + fixes (#23411)

* Chill bounties + fixes

* localize

* fix arbitage
This commit is contained in:
Nemanja
2024-01-03 19:34:47 -05:00
committed by GitHub
parent 9d8ac7846a
commit 4662d463b8
22 changed files with 284 additions and 182 deletions

View File

@@ -1,7 +1,6 @@
using Content.Client.Cargo.UI; using Content.Client.Cargo.UI;
using Content.Shared.Cargo.Components; using Content.Shared.Cargo.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects;
namespace Content.Client.Cargo.BUI; namespace Content.Client.Cargo.BUI;

View File

@@ -8,14 +8,13 @@
HorizontalExpand="True"> HorizontalExpand="True">
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<BoxContainer Orientation="Vertical" HorizontalExpand="True"> <BoxContainer Orientation="Vertical" HorizontalExpand="True">
<RichTextLabel Name="TimeLabel"/>
<RichTextLabel Name="RewardLabel"/> <RichTextLabel Name="RewardLabel"/>
<RichTextLabel Name="ManifestLabel"/> <RichTextLabel Name="ManifestLabel"/>
</BoxContainer> </BoxContainer>
<Control MinWidth="10"/> <Control MinWidth="10"/>
<BoxContainer Orientation="Vertical" MinWidth="120"> <BoxContainer Orientation="Vertical" MinWidth="120">
<Button Name="PrintButton" Text="{Loc 'bounty-console-label-button-text'}" HorizontalExpand="False" HorizontalAlignment="Right"/> <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>
</BoxContainer> </BoxContainer>
<customControls:HSeparator Margin="5 10 5 10"/> <customControls:HSeparator Margin="5 10 5 10"/>

View File

@@ -27,8 +27,6 @@ public sealed partial class BountyEntry : BoxContainer
if (!_prototype.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype)) if (!_prototype.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype))
return; return;
EndTime = bounty.EndTime;
var items = new List<string>(); var items = new List<string>();
foreach (var entry in bountyPrototype.Entries) 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)))); ManifestLabel.SetMarkup(Loc.GetString("bounty-console-manifest-label", ("item", string.Join(", ", items))));
RewardLabel.SetMarkup(Loc.GetString("bounty-console-reward-label", ("reward", bountyPrototype.Reward))); RewardLabel.SetMarkup(Loc.GetString("bounty-console-reward-label", ("reward", bountyPrototype.Reward)));
DescriptionLabel.SetMarkup(Loc.GetString("bounty-console-description-label", ("description", Loc.GetString(bountyPrototype.Description)))); 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(); 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"))));
}
} }

View File

@@ -9,7 +9,7 @@ namespace Content.Client.Cargo.UI;
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class CargoBountyMenu : FancyWindow public sealed partial class CargoBountyMenu : FancyWindow
{ {
public Action<int>? OnLabelButtonPressed; public Action<string>? OnLabelButtonPressed;
public CargoBountyMenu() public CargoBountyMenu()
{ {

View File

@@ -80,7 +80,7 @@ public sealed class CargoTest
foreach (var bounty in bounties) foreach (var bounty in bounties)
{ {
if (cargo.IsBountyComplete(ent, bounty)) 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); entManager.DeleteEntity(ent);

View File

@@ -10,8 +10,8 @@ public sealed partial class CargoBountyLabelComponent : Component
/// <summary> /// <summary>
/// The ID for the bounty this label corresponds to. /// The ID for the bounty this label corresponds to.
/// </summary> /// </summary>
[DataField("id"), ViewVariables(VVAccess.ReadWrite)] [DataField, ViewVariables(VVAccess.ReadWrite)]
public int Id; public string Id = string.Empty;
/// <summary> /// <summary>
/// Used to prevent recursion in calculating the price. /// Used to prevent recursion in calculating the price.

View File

@@ -11,37 +11,25 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
/// <summary> /// <summary>
/// Maximum amount of bounties a station can have. /// Maximum amount of bounties a station can have.
/// </summary> /// </summary>
[DataField("maxBounties"), ViewVariables(VVAccess.ReadWrite)] [DataField, ViewVariables(VVAccess.ReadWrite)]
public int MaxBounties = 3; public int MaxBounties = 5;
/// <summary> /// <summary>
/// A list of all the bounties currently active for a station. /// A list of all the bounties currently active for a station.
/// </summary> /// </summary>
[DataField("bounties"), ViewVariables(VVAccess.ReadWrite)] [DataField, ViewVariables(VVAccess.ReadWrite)]
public List<CargoBountyData> Bounties = new(); public List<CargoBountyData> Bounties = new();
/// <summary> /// <summary>
/// Used to determine unique order IDs /// Used to determine unique order IDs
/// </summary> /// </summary>
[DataField("totalBounties")] [DataField]
public int TotalBounties; 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> /// <summary>
/// A list of bounty IDs that have been checked this tick. /// A list of bounty IDs that have been checked this tick.
/// Used to prevent multiplying bounty prices. /// Used to prevent multiplying bounty prices.
/// </summary> /// </summary>
[DataField] [DataField]
public HashSet<int> CheckedBounties = new(); public HashSet<string> CheckedBounties = new();
} }

View File

@@ -2,14 +2,16 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Content.Server.Cargo.Components; using Content.Server.Cargo.Components;
using Content.Server.Labels; using Content.Server.Labels;
using Content.Server.NameIdentifier;
using Content.Server.Paper; using Content.Server.Paper;
using Content.Shared.Cargo; using Content.Shared.Cargo;
using Content.Shared.Cargo.Components; using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Prototypes; using Content.Shared.Cargo.Prototypes;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.NameIdentifier;
using Content.Shared.Stacks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.Containers; using Robust.Server.Containers;
using Robust.Shared.Collections;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -19,6 +21,14 @@ namespace Content.Server.Cargo.Systems;
public sealed partial class CargoSystem public sealed partial class CargoSystem
{ {
[Dependency] private readonly ContainerSystem _container = default!; [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() private void InitializeBounty()
{ {
@@ -27,6 +37,10 @@ public sealed partial class CargoSystem
SubscribeLocalEvent<CargoBountyLabelComponent, PriceCalculationEvent>(OnGetBountyPrice); SubscribeLocalEvent<CargoBountyLabelComponent, PriceCalculationEvent>(OnGetBountyPrice);
SubscribeLocalEvent<EntitySoldEvent>(OnSold); SubscribeLocalEvent<EntitySoldEvent>(OnSold);
SubscribeLocalEvent<StationCargoBountyDatabaseComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<StationCargoBountyDatabaseComponent, MapInitEvent>(OnMapInit);
_stackQuery = GetEntityQuery<StackComponent>();
_containerQuery = GetEntityQuery<ContainerManagerComponent>();
_bountyLabelQuery = GetEntityQuery<CargoBountyLabelComponent>();
} }
private void OnBountyConsoleOpened(EntityUid uid, CargoBountyConsoleComponent component, BoundUIOpenedEvent args) 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)) if (!TryGetBountyFromId(station, component.Id, out var bounty, database))
return; return;
if (!_protoMan.TryIndex<CargoBountyPrototype>(bounty.Value.Bounty, out var bountyPrototype) || if (!_protoMan.TryIndex(bounty.Value.Bounty, out var bountyPrototype) ||
!IsBountyComplete(container.Owner, bountyPrototype)) !IsBountyComplete(container.Owner, bountyPrototype))
return; return;
@@ -112,25 +126,15 @@ public sealed partial class CargoSystem
private void OnSold(ref EntitySoldEvent args) private void OnSold(ref EntitySoldEvent args)
{ {
var containerQuery = GetEntityQuery<ContainerManagerComponent>();
var labelQuery = GetEntityQuery<CargoBountyLabelComponent>();
foreach (var sold in args.Sold) foreach (var sold in args.Sold)
{ {
if (!containerQuery.TryGetComponent(sold, out var containerMan)) if (!TryGetBountyLabel(sold, out _, out var component))
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))
continue; continue;
if (!TryGetBountyFromId(args.Station, component.Id, out var bounty)) if (!TryGetBountyFromId(args.Station, component.Id, out var bounty))
continue; continue;
if (!IsBountyComplete(container.Owner, bounty.Value)) if (!IsBountyComplete(sold, bounty.Value))
continue; continue;
TryRemoveBounty(args.Station, bounty.Value); 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) private void OnMapInit(EntityUid uid, StationCargoBountyDatabaseComponent component, MapInitEvent args)
{ {
FillBountyDatabase(uid, component); FillBountyDatabase(uid, component);
@@ -161,12 +187,53 @@ public sealed partial class CargoSystem
UpdateBountyConsoles(); 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) public bool IsBountyComplete(EntityUid container, CargoBountyData data)
{ {
if (!_protoMan.TryIndex<CargoBountyPrototype>(data.Bounty, out var proto)) return IsBountyComplete(container, data, out _);
return false; }
return IsBountyComplete(container, proto.Entries); 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, out bountyEntities);
} }
public bool IsBountyComplete(EntityUid container, string id) public bool IsBountyComplete(EntityUid container, string id)
@@ -184,26 +251,18 @@ public sealed partial class CargoSystem
public bool IsBountyComplete(EntityUid container, IEnumerable<CargoBountyItemEntry> entries) public bool IsBountyComplete(EntityUid container, IEnumerable<CargoBountyItemEntry> entries)
{ {
var contained = new HashSet<EntityUid>(); return IsBountyComplete(container, entries, out _);
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(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) foreach (var entry in entries)
{ {
var count = 0; var count = 0;
@@ -215,7 +274,8 @@ public sealed partial class CargoSystem
{ {
if (!entry.Whitelist.IsValid(entity, EntityManager)) if (!entry.Whitelist.IsValid(entity, EntityManager))
continue; continue;
count++;
count += _stackQuery.CompOrNull(entity)?.Count ?? 1;
temp.Add(entity); temp.Add(entity);
if (count >= entry.Amount) if (count >= entry.Amount)
@@ -228,17 +288,58 @@ public sealed partial class CargoSystem
foreach (var ent in temp) foreach (var ent in temp)
{ {
entities.Remove(ent); entities.Remove(ent);
bountyEntities.Add(ent);
} }
} }
return true; 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] [PublicAPI]
public bool TryAddBounty(EntityUid uid, StationCargoBountyDatabaseComponent? component = null) public bool TryAddBounty(EntityUid uid, StationCargoBountyDatabaseComponent? component = null)
{ {
if (!Resolve(uid, ref component))
return false;
// todo: consider making the cargo bounties weighted. // 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); return TryAddBounty(uid, bounty, component);
} }
@@ -261,17 +362,15 @@ public sealed partial class CargoSystem
if (component.Bounties.Count >= component.MaxBounties) if (component.Bounties.Count >= component.MaxBounties)
return false; return false;
var duration = MathF.Round(_random.NextFloat(component.MinBountyTime, component.MaxBountyTime) / 15) * 15; _nameIdentifier.GenerateUniqueName(uid, BountyNameIdentifierGroup, out var randomVal);
var endTime = _timing.CurTime + TimeSpan.FromSeconds(duration); component.Bounties.Add(new CargoBountyData(bounty, randomVal));
component.Bounties.Add(new CargoBountyData(component.TotalBounties, bounty.ID, endTime));
_adminLogger.Add(LogType.Action, LogImpact.Low, $"Added bounty \"{bounty.ID}\" (id:{component.TotalBounties}) to station {ToPrettyString(uid)}"); _adminLogger.Add(LogType.Action, LogImpact.Low, $"Added bounty \"{bounty.ID}\" (id:{component.TotalBounties}) to station {ToPrettyString(uid)}");
component.TotalBounties++; component.TotalBounties++;
return true; return true;
} }
[PublicAPI] [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)) if (!TryGetBountyFromId(uid, dataId, out var data, component))
return false; return false;
@@ -298,7 +397,7 @@ public sealed partial class CargoSystem
public bool TryGetBountyFromId( public bool TryGetBountyFromId(
EntityUid uid, EntityUid uid,
int id, string id,
[NotNullWhen(true)] out CargoBountyData? bounty, [NotNullWhen(true)] out CargoBountyData? bounty,
StationCargoBountyDatabaseComponent? component = null) StationCargoBountyDatabaseComponent? component = null)
{ {
@@ -333,17 +432,9 @@ public sealed partial class CargoSystem
private void UpdateBounty() private void UpdateBounty()
{ {
var query = EntityQueryEnumerator<StationCargoBountyDatabaseComponent>(); var query = EntityQueryEnumerator<StationCargoBountyDatabaseComponent>();
while (query.MoveNext(out var uid, out var bountyDatabase)) while (query.MoveNext(out var bountyDatabase))
{ {
bountyDatabase.CheckedBounties.Clear(); 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);
}
} }
} }
} }

View File

@@ -288,10 +288,15 @@ public sealed partial class CargoSystem
return false; return false;
} }
var complete = IsBountyComplete(uid, (EntityUid?) null, out var bountyEntities);
// Recursively check for mobs at any point. // Recursively check for mobs at any point.
var children = xform.ChildEnumerator; var children = xform.ChildEnumerator;
while (children.MoveNext(out var child)) while (children.MoveNext(out var child))
{ {
if (complete && bountyEntities.Contains(child))
continue;
if (!CanSell(child, _xformQuery.GetComponent(child))) if (!CanSell(child, _xformQuery.GetComponent(child)))
return false; return false;
} }

View File

@@ -13,7 +13,6 @@ using Content.Shared.Containers.ItemSlots;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Map; using Robust.Shared.Map;

View 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> /// <summary>
/// Generates a new unique name/suffix for a given entity and adds it to <see cref="CurrentIds"/> /// 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. /// but does not set the entity's name.

View File

@@ -1,6 +1,7 @@
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using Content.Server.Administration; using Content.Server.Administration;
using Content.Server.Cargo.Systems;
using Content.Server.Station.Components; using Content.Server.Station.Components;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Shared.Administration; using Content.Shared.Administration;
@@ -15,6 +16,7 @@ namespace Content.Server.Station.Commands;
public sealed class StationsCommand : ToolshedCommand public sealed class StationsCommand : ToolshedCommand
{ {
private StationSystem? _station; private StationSystem? _station;
private CargoSystem? _cargo;
[CommandImplementation("list")] [CommandImplementation("list")]
public IEnumerable<EntityUid> List() public IEnumerable<EntityUid> List()
@@ -111,6 +113,15 @@ public sealed class StationsCommand : ToolshedCommand
_station.RenameStation(input, name.Evaluate(ctx)!); _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 public record struct OnlyOneStationsError : IConError

View File

@@ -1,7 +1,6 @@
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Content.Shared.Cargo.Prototypes; using Content.Shared.Cargo.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Cargo; namespace Content.Shared.Cargo;
@@ -9,28 +8,24 @@ namespace Content.Shared.Cargo;
/// A data structure for storing currently available bounties. /// A data structure for storing currently available bounties.
/// </summary> /// </summary>
[DataDefinition, NetSerializable, Serializable] [DataDefinition, NetSerializable, Serializable]
public readonly partial record struct CargoBountyData(int Id, string Bounty, TimeSpan EndTime) public readonly partial record struct CargoBountyData
{ {
/// <summary> /// <summary>
/// A numeric id used to identify the bounty /// A unique id used to identify the bounty
/// </summary> /// </summary>
[DataField("id"), ViewVariables(VVAccess.ReadWrite)] [DataField, ViewVariables(VVAccess.ReadWrite)]
public int Id { get; init; } = Id; public string Id { get; init; } = string.Empty;
/// <summary> /// <summary>
/// The prototype containing information about the bounty. /// The prototype containing information about the bounty.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
[DataField("bounty", customTypeSerializer: typeof(PrototypeIdSerializer<CargoBountyPrototype>), required:true)] [DataField(required: true)]
public string Bounty { get; init; } = Bounty; public ProtoId<CargoBountyPrototype> Bounty { get; init; } = string.Empty;
/// <summary> public CargoBountyData(CargoBountyPrototype bounty, int uniqueIdentifier)
/// 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)
{ {
Bounty = bounty.ID;
Id = $"{bounty.IdPrefix}{uniqueIdentifier:D3}";
} }
} }

View File

@@ -48,9 +48,9 @@ public sealed class CargoBountyConsoleState : BoundUserInterfaceState
[Serializable, NetSerializable] [Serializable, NetSerializable]
public sealed class BountyPrintLabelMessage : BoundUserInterfaceMessage public sealed class BountyPrintLabelMessage : BoundUserInterfaceMessage
{ {
public int BountyId; public string BountyId;
public BountyPrintLabelMessage(int bountyId) public BountyPrintLabelMessage(string bountyId)
{ {
BountyId = bountyId; BountyId = bountyId;
} }

View File

@@ -9,7 +9,7 @@ namespace Content.Shared.Cargo.Prototypes;
/// that must be sold together in a labeled container in order /// that must be sold together in a labeled container in order
/// to receive a monetary reward. /// to receive a monetary reward.
/// </summary> /// </summary>
[Prototype("cargoBounty"), Serializable, NetSerializable] [Prototype, Serializable, NetSerializable]
public sealed partial class CargoBountyPrototype : IPrototype public sealed partial class CargoBountyPrototype : IPrototype
{ {
/// <inheritdoc/> /// <inheritdoc/>
@@ -19,20 +19,26 @@ public sealed partial class CargoBountyPrototype : IPrototype
/// <summary> /// <summary>
/// The monetary reward for completing the bounty /// The monetary reward for completing the bounty
/// </summary> /// </summary>
[DataField("reward", required: true)] [DataField(required: true)]
public int Reward; public int Reward;
/// <summary> /// <summary>
/// A description for flava purposes. /// A description for flava purposes.
/// </summary> /// </summary>
[DataField("description")] [DataField]
public string Description = string.Empty; public LocId Description = string.Empty;
/// <summary> /// <summary>
/// The entries that must be satisfied for the cargo bounty to be complete. /// The entries that must be satisfied for the cargo bounty to be complete.
/// </summary> /// </summary>
[DataField("entries", required: true)] [DataField( required: true)]
public List<CargoBountyItemEntry> Entries = new(); 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] [DataDefinition, Serializable, NetSerializable]
@@ -41,7 +47,7 @@ public readonly partial record struct CargoBountyItemEntry()
/// <summary> /// <summary>
/// A whitelist for determining what items satisfy the entry. /// A whitelist for determining what items satisfy the entry.
/// </summary> /// </summary>
[DataField("whitelist", required: true)] [DataField(required: true)]
public EntityWhitelist Whitelist { get; init; } = default!; public EntityWhitelist Whitelist { get; init; } = default!;
// todo: implement some kind of simple generic condition system // todo: implement some kind of simple generic condition system
@@ -49,12 +55,12 @@ public readonly partial record struct CargoBountyItemEntry()
/// <summary> /// <summary>
/// How much of the item must be present to satisfy the entry /// How much of the item must be present to satisfy the entry
/// </summary> /// </summary>
[DataField("amount")] [DataField]
public int Amount { get; init; } = 1; public int Amount { get; init; } = 1;
/// <summary> /// <summary>
/// A player-facing name for the item. /// A player-facing name for the item.
/// </summary> /// </summary>
[DataField("name")] [DataField]
public string Name { get; init; } = string.Empty; public LocId Name { get; init; } = string.Empty;
} }

View File

@@ -31,7 +31,7 @@ bounty-item-pen = Pen
bounty-item-percussion = Percussion instrument bounty-item-percussion = Percussion instrument
bounty-item-pie = Pie bounty-item-pie = Pie
bounty-item-prison-uniform = Prison uniform bounty-item-prison-uniform = Prison uniform
bounty-item-radio = Radio bounty-item-radio = Radio or Headset
bounty-item-research-disk = Research disk bounty-item-research-disk = Research disk
bounty-item-shiv = Shiv bounty-item-shiv = Shiv
bounty-item-soap = Soap bounty-item-soap = Soap

View File

@@ -2,7 +2,7 @@
bounty-console-label-button-text = Print label bounty-console-label-button-text = Print label
bounty-console-time-label = Time: [color=orange]{$time}[/color] bounty-console-time-label = Time: [color=orange]{$time}[/color]
bounty-console-reward-label = Reward: [color=limegreen]${$reward}[/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 = bounty-console-manifest-entry =
{ $amount -> { $amount ->
[1] {$item} [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-left = Bounties sourced from local unscrupulous dealers.
bounty-console-flavor-right = v1.4 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: bounty-manifest-list-start = Item manifest:

View File

@@ -40,6 +40,8 @@ command-description-stations-rename =
Renames the given station. Renames the given station.
command-description-stations-largestgrid = command-description-stations-largestgrid =
Returns the largest grid the given station has, if any. 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 = command-description-stationevent-lsprob =
Lists the probability of different station events occuring out of the entire pool. Lists the probability of different station events occuring out of the entire pool.
command-description-stationevent-lsprobtime = command-description-stationevent-lsprobtime =

View File

@@ -11,8 +11,9 @@
- type: cargoBounty - type: cargoBounty
id: BountyBaseballBat id: BountyBaseballBat
reward: 8000 reward: 4000
description: bounty-description-baseball-bat description: bounty-description-baseball-bat
idPrefix: CC
entries: entries:
- name: bounty-item-baseball-bat - name: bounty-item-baseball-bat
amount: 5 amount: 5
@@ -22,7 +23,7 @@
- type: cargoBounty - type: cargoBounty
id: BountyBoxHug id: BountyBoxHug
reward: 6000 reward: 3000
description: bounty-description-box-hugs description: bounty-description-box-hugs
entries: entries:
- name: bounty-item-box-hugs - name: bounty-item-box-hugs
@@ -33,7 +34,7 @@
- type: cargoBounty - type: cargoBounty
id: BountyBrain id: BountyBrain
reward: 25000 reward: 12500
description: bounty-description-brain description: bounty-description-brain
entries: entries:
- name: bounty-item-brain - name: bounty-item-brain
@@ -44,7 +45,7 @@
- type: cargoBounty - type: cargoBounty
id: BountyBread id: BountyBread
reward: 4000 reward: 2000
description: bounty-description-bread description: bounty-description-bread
entries: entries:
- name: bounty-item-bread - name: bounty-item-bread
@@ -55,8 +56,9 @@
- type: cargoBounty - type: cargoBounty
id: BountyBriefcase id: BountyBriefcase
reward: 15000 reward: 7500
description: bounty-description-briefcase description: bounty-description-briefcase
idPrefix: CC
entries: entries:
- name: bounty-item-briefcase - name: bounty-item-briefcase
amount: 5 amount: 5
@@ -66,8 +68,9 @@
- type: cargoBounty - type: cargoBounty
id: BountyCarrot id: BountyCarrot
reward: 15000 reward: 7500
description: bounty-description-carrot description: bounty-description-carrot
idPrefix: SS15-
entries: entries:
- name: bounty-item-carrot - name: bounty-item-carrot
amount: 10 amount: 10
@@ -77,7 +80,7 @@
- type: cargoBounty - type: cargoBounty
id: BountyCarrotFries id: BountyCarrotFries
reward: 14000 reward: 7000
description: bounty-description-carrot-fries description: bounty-description-carrot-fries
entries: entries:
- name: bounty-item-carrot-fries - name: bounty-item-carrot-fries
@@ -88,8 +91,9 @@
- type: cargoBounty - type: cargoBounty
id: BountyCarp id: BountyCarp
reward: 25000 reward: 12500
description: bounty-description-carp description: bounty-description-carp
idPrefix: CC
entries: entries:
- name: bounty-item-carp - name: bounty-item-carp
amount: 1 amount: 1
@@ -115,7 +119,7 @@
- type: cargoBounty - type: cargoBounty
id: BountyCorn id: BountyCorn
reward: 20000 reward: 10000
description: bounty-description-corn description: bounty-description-corn
entries: entries:
- name: bounty-item-corn - name: bounty-item-corn
@@ -126,7 +130,7 @@
- type: cargoBounty - type: cargoBounty
id: BountyCrayon id: BountyCrayon
reward: 8000 reward: 4000
description: bounty-description-crayon description: bounty-description-crayon
entries: entries:
- name: bounty-item-crayon - name: bounty-item-crayon
@@ -137,8 +141,9 @@
- type: cargoBounty - type: cargoBounty
id: BountyCubanCarp id: BountyCubanCarp
reward: 32000 reward: 16000
description: bounty-description-cuban-carp description: bounty-description-cuban-carp
idPrefix: CC
entries: entries:
- name: bounty-item-cuban-carp - name: bounty-item-cuban-carp
amount: 1 amount: 1
@@ -148,8 +153,9 @@
- type: cargoBounty - type: cargoBounty
id: BountyDonkPocket id: BountyDonkPocket
reward: 12000 reward: 6000
description: bounty-description-donk-pocket description: bounty-description-donk-pocket
idPrefix: CC
entries: entries:
- name: bounty-item-donk-pocket - name: bounty-item-donk-pocket
amount: 12 amount: 12
@@ -159,8 +165,9 @@
- type: cargoBounty - type: cargoBounty
id: BountyDonut id: BountyDonut
reward: 12000 reward: 6000
description: bounty-description-donut description: bounty-description-donut
idPrefix: CC
entries: entries:
- name: bounty-item-donut - name: bounty-item-donut
amount: 10 amount: 10
@@ -170,7 +177,7 @@
- type: cargoBounty - type: cargoBounty
id: BountyFigurine id: BountyFigurine
reward: 16000 reward: 8000
description: bounty-description-figurine description: bounty-description-figurine
entries: entries:
- name: bounty-item-figurine - name: bounty-item-figurine
@@ -179,21 +186,11 @@
tags: tags:
- Figurine - 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 - type: cargoBounty
id: BountyFlower id: BountyFlower
reward: 4000 reward: 2000
description: bounty-description-flower description: bounty-description-flower
idPrefix: CC
entries: entries:
- name: bounty-item-flower - name: bounty-item-flower
amount: 3 amount: 3
@@ -203,7 +200,7 @@
- type: cargoBounty - type: cargoBounty
id: BountyGalaxyThistle id: BountyGalaxyThistle
reward: 24000 reward: 12000
description: bounty-description-galaxythistle description: bounty-description-galaxythistle
entries: entries:
- name: bounty-item-galaxythistle - name: bounty-item-galaxythistle
@@ -214,8 +211,9 @@
- type: cargoBounty - type: cargoBounty
id: BountyHandcuffs id: BountyHandcuffs
reward: 4000 reward: 1000
description: bounty-description-handcuffs description: bounty-description-handcuffs
idPrefix: CC
entries: entries:
- name: bounty-item-handcuffs - name: bounty-item-handcuffs
amount: 5 amount: 5
@@ -236,7 +234,7 @@
- type: cargoBounty - type: cargoBounty
id: BountyKnife id: BountyKnife
reward: 12000 reward: 6000
description: bounty-description-knife description: bounty-description-knife
entries: entries:
- name: bounty-item-knife - name: bounty-item-knife
@@ -247,7 +245,7 @@
- type: cargoBounty - type: cargoBounty
id: BountyLemon id: BountyLemon
reward: 20000 reward: 10000
description: bounty-description-lemon description: bounty-description-lemon
entries: entries:
- name: bounty-item-lemon - name: bounty-item-lemon
@@ -258,8 +256,9 @@
- type: cargoBounty - type: cargoBounty
id: BountyLime id: BountyLime
reward: 20000 reward: 10000
description: bounty-description-lime description: bounty-description-lime
idPrefix: CC
entries: entries:
- name: bounty-item-lime - name: bounty-item-lime
amount: 7 amount: 7
@@ -269,7 +268,7 @@
- type: cargoBounty - type: cargoBounty
id: BountyLung id: BountyLung
reward: 35000 reward: 2000
description: bounty-description-lung description: bounty-description-lung
entries: entries:
- name: bounty-item-lung - name: bounty-item-lung
@@ -280,8 +279,9 @@
- type: cargoBounty - type: cargoBounty
id: BountyMonkeyCube id: BountyMonkeyCube
reward: 8000 reward: 2000
description: bounty-description-monkey-cube description: bounty-description-monkey-cube
idPrefix: CC
entries: entries:
- name: bounty-item-monkey-cube - name: bounty-item-monkey-cube
amount: 3 amount: 3
@@ -293,6 +293,7 @@
id: BountyMouse id: BountyMouse
reward: 2400 reward: 2400
description: bounty-description-mouse description: bounty-description-mouse
idPrefix: SS13-
entries: entries:
- name: bounty-item-mouse - name: bounty-item-mouse
amount: 5 amount: 5
@@ -302,7 +303,7 @@
- type: cargoBounty - type: cargoBounty
id: BountyPancake id: BountyPancake
reward: 20000 reward: 10000
description: bounty-description-pancake description: bounty-description-pancake
entries: entries:
- name: bounty-item-pancake - name: bounty-item-pancake
@@ -317,14 +318,14 @@
description: bounty-description-pen description: bounty-description-pen
entries: entries:
- name: bounty-item-pen - name: bounty-item-pen
amount: 10 amount: 5
whitelist: whitelist:
tags: tags:
- Write - Pen
- type: cargoBounty - type: cargoBounty
id: BountyPercussion id: BountyPercussion
reward: 25000 reward: 15000
description: bounty-description-percussion description: bounty-description-percussion
entries: entries:
- name: bounty-item-percussion - name: bounty-item-percussion
@@ -335,7 +336,7 @@
- type: cargoBounty - type: cargoBounty
id: BountyPie id: BountyPie
reward: 31415 reward: 3141
description: bounty-description-pie description: bounty-description-pie
entries: entries:
- name: bounty-item-pie - name: bounty-item-pie
@@ -348,6 +349,7 @@
id: BountyPrisonUniform id: BountyPrisonUniform
reward: 8000 reward: 8000
description: bounty-description-prison-uniform description: bounty-description-prison-uniform
idPrefix: TG
entries: entries:
- name: bounty-item-prison-uniform - name: bounty-item-prison-uniform
amount: 4 amount: 4
@@ -357,7 +359,7 @@
- type: cargoBounty - type: cargoBounty
id: BountyRadio id: BountyRadio
reward: 10000 reward: 7500
description: bounty-description-radio description: bounty-description-radio
entries: entries:
- name: bounty-item-radio - name: bounty-item-radio
@@ -369,21 +371,11 @@
tags: tags:
- Radio - 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 - type: cargoBounty
id: BountyShiv id: BountyShiv
reward: 8000 reward: 4000
description: bounty-description-shiv description: bounty-description-shiv
idPrefix: SYN
entries: entries:
- name: bounty-item-shiv - name: bounty-item-shiv
amount: 5 amount: 5
@@ -393,7 +385,7 @@
- type: cargoBounty - type: cargoBounty
id: BountySoap id: BountySoap
reward: 8000 reward: 4000
description: bounty-description-soap description: bounty-description-soap
entries: entries:
- name: bounty-item-soap - name: bounty-item-soap
@@ -404,7 +396,7 @@
- type: cargoBounty - type: cargoBounty
id: BountySoup id: BountySoup
reward: 12000 reward: 6000
description: bounty-description-soup description: bounty-description-soup
entries: entries:
- name: bounty-item-soup - name: bounty-item-soup
@@ -426,7 +418,7 @@
- type: cargoBounty - type: cargoBounty
id: BountySyringe id: BountySyringe
reward: 10000 reward: 5000
description: bounty-description-syringe description: bounty-description-syringe
entries: entries:
- name: bounty-item-syringe - name: bounty-item-syringe
@@ -439,6 +431,7 @@
id: BountyTechDisk id: BountyTechDisk
reward: 25000 reward: 25000
description: bounty-description-tech-disk description: bounty-description-tech-disk
idPrefix: SS13-
entries: entries:
- name: bounty-item-tech-disk - name: bounty-item-tech-disk
amount: 5 amount: 5
@@ -450,6 +443,7 @@
id: BountyToolbox id: BountyToolbox
reward: 8000 reward: 8000
description: bounty-description-toolbox description: bounty-description-toolbox
idPrefix: CC
entries: entries:
- name: bounty-item-toolbox - name: bounty-item-toolbox
amount: 6 amount: 6
@@ -459,7 +453,7 @@
- type: cargoBounty - type: cargoBounty
id: BountyTrash id: BountyTrash
reward: 700 reward: 500
description: bounty-description-trash description: bounty-description-trash
entries: entries:
- name: bounty-item-trash - name: bounty-item-trash
@@ -503,8 +497,9 @@
- type: cargoBounty - type: cargoBounty
id: BountyOrgans id: BountyOrgans
reward: 3500 reward: 2000
description: bounty-description-organs description: bounty-description-organs
idPrefix: UNTH
entries: entries:
- name: bounty-item-organs - name: bounty-item-organs
amount: 8 amount: 8
@@ -525,8 +520,9 @@
- type: cargoBounty - type: cargoBounty
id: BountyWarmCloth id: BountyWarmCloth
reward: 6000 reward: 2000
description: bounty-description-warm-cloth description: bounty-description-warm-cloth
idPrefix: UNTH
entries: entries:
- name: bounty-item-warm-cloth - name: bounty-item-warm-cloth
amount: 8 amount: 8
@@ -536,8 +532,9 @@
- type: cargoBounty - type: cargoBounty
id: BountyBattery id: BountyBattery
reward: 24500 reward: 15000
description: bounty-description-battery description: bounty-description-battery
idPrefix: UNTH
entries: entries:
- name: bounty-item-battery - name: bounty-item-battery
amount: 15 amount: 15
@@ -549,6 +546,7 @@
id: BountyLaserGun id: BountyLaserGun
reward: 28500 reward: 28500
description: bounty-description-lasergun description: bounty-description-lasergun
idPrefix: IV
entries: entries:
- name: bounty-lasergun - name: bounty-lasergun
amount: 6 amount: 6
@@ -558,8 +556,9 @@
- type: cargoBounty - type: cargoBounty
id: BountyFood id: BountyFood
reward: 9500 reward: 4000
description: bounty-description-food description: bounty-description-food
idPrefix: UNTH
entries: entries:
- name: bounty-food - name: bounty-food
amount: 30 amount: 30

View File

@@ -239,6 +239,7 @@
- type: Tag - type: Tag
tags: tags:
- Write - Write
- Pen
- type: Sprite - type: Sprite
sprite: Objects/Misc/bureaucracy.rsi sprite: Objects/Misc/bureaucracy.rsi
state: pen state: pen

View File

@@ -35,3 +35,8 @@
# Used to suffix a number to any mob to identify player controlled mob griefing. # Used to suffix a number to any mob to identify player controlled mob griefing.
- type: nameIdentifierGroup - type: nameIdentifierGroup
id: GenericNumber id: GenericNumber
- type: nameIdentifierGroup
id: Bounty
minValue: 0
maxValue: 999

View File

@@ -830,6 +830,9 @@
- type: Tag - type: Tag
id: Pancake id: Pancake
- type: Tag
id: Pen
- type: Tag - type: Tag
id: PepperShaker id: PepperShaker