Cargo Bounties (#17344)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
using Content.Client.Cargo.UI;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Cargo.BUI;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private CargoBountyMenu? _menu;
|
||||
|
||||
public CargoBountyConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = new();
|
||||
|
||||
_menu.OnClose += Close;
|
||||
|
||||
_menu.OnLabelButtonPressed += id =>
|
||||
{
|
||||
SendMessage(new BountyPrintLabelMessage(id));
|
||||
};
|
||||
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState message)
|
||||
{
|
||||
base.UpdateState(message);
|
||||
|
||||
if (message is not CargoBountyConsoleState state)
|
||||
return;
|
||||
|
||||
_menu?.UpdateEntries(state.Bounties);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_menu?.Dispose();
|
||||
}
|
||||
}
|
||||
27
Content.Client/Cargo/UI/BountyEntry.xaml
Normal file
27
Content.Client/Cargo/UI/BountyEntry.xaml
Normal file
@@ -0,0 +1,27 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Margin="10 10 10 0"
|
||||
HorizontalExpand="True"
|
||||
Visible="True">
|
||||
<PanelContainer StyleClasses="AngleRect" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
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"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<customControls:HSeparator Margin="5 10 5 10"/>
|
||||
<BoxContainer>
|
||||
<RichTextLabel Name="DescriptionLabel"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
54
Content.Client/Cargo/UI/BountyEntry.xaml.cs
Normal file
54
Content.Client/Cargo/UI/BountyEntry.xaml.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Content.Client.Message;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Cargo.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class BountyEntry : BoxContainer
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public Action? OnButtonPressed;
|
||||
|
||||
public TimeSpan EndTime;
|
||||
|
||||
public BountyEntry(CargoBountyData bounty)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
if (!_prototype.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype))
|
||||
return;
|
||||
|
||||
EndTime = bounty.EndTime;
|
||||
|
||||
var items = new List<string>();
|
||||
foreach (var entry in bountyPrototype.Entries)
|
||||
{
|
||||
items.Add(Loc.GetString("bounty-console-manifest-entry",
|
||||
("amount", entry.Amount),
|
||||
("item", Loc.GetString(entry.Name))));
|
||||
}
|
||||
ManifestLabel.SetMarkup(Loc.GetString("bounty-console-manifest-label", ("item", string.Join(", ", items))));
|
||||
RewardLabel.SetMarkup(Loc.GetString("bounty-console-reward-label", ("reward", bountyPrototype.Reward)));
|
||||
DescriptionLabel.SetMarkup(Loc.GetString("bounty-console-description-label", ("description", Loc.GetString(bountyPrototype.Description))));
|
||||
IdLabel.Text = 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"))));
|
||||
}
|
||||
}
|
||||
36
Content.Client/Cargo/UI/CargoBountyMenu.xaml
Normal file
36
Content.Client/Cargo/UI/CargoBountyMenu.xaml
Normal file
@@ -0,0 +1,36 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'bounty-console-menu-title'}"
|
||||
SetSize="550 420"
|
||||
MinSize="400 350">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
<PanelContainer VerticalExpand="True" HorizontalExpand="True" Margin="10">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<ScrollContainer HScrollEnabled="False"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<BoxContainer Name="BountyEntriesContainer"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
<!-- Footer -->
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
|
||||
<Label Text="{Loc 'bounty-console-flavor-left'}" StyleClasses="WindowFooterText" />
|
||||
<Label Text="{Loc 'bounty-console-flavor-right'}" StyleClasses="WindowFooterText"
|
||||
HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 5 0" />
|
||||
<TextureRect StyleClasses="NTLogoDark" Stretch="KeepAspectCentered"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Right" SetSize="19 19"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
34
Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
Normal file
34
Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Cargo;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Cargo.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CargoBountyMenu : FancyWindow
|
||||
{
|
||||
public Action<int>? OnLabelButtonPressed;
|
||||
|
||||
public CargoBountyMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void UpdateEntries(List<CargoBountyData> bounties)
|
||||
{
|
||||
BountyEntriesContainer.Children.Clear();
|
||||
foreach (var b in bounties)
|
||||
{
|
||||
var entry = new BountyEntry(b);
|
||||
entry.OnButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
|
||||
|
||||
BountyEntriesContainer.AddChild(entry);
|
||||
}
|
||||
BountyEntriesContainer.AddChild(new Control
|
||||
{
|
||||
MinHeight = 10
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,46 @@ public sealed class CargoTest
|
||||
|
||||
await pairTracker.CleanReturnAsync();
|
||||
}
|
||||
[Test]
|
||||
public async Task NoCargoBountyArbitageTest()
|
||||
{
|
||||
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings() {NoClient = true});
|
||||
var server = pairTracker.Pair.Server;
|
||||
|
||||
var testMap = await PoolManager.CreateTestMap(pairTracker);
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var protoManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var cargo = entManager.System<CargoSystem>();
|
||||
|
||||
var bounties = protoManager.EnumeratePrototypes<CargoBountyPrototype>().ToList();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var mapId = testMap.MapId;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var proto in protoManager.EnumeratePrototypes<CargoProductPrototype>())
|
||||
{
|
||||
var ent = entManager.SpawnEntity(proto.Product, new MapCoordinates(Vector2.Zero, mapId));
|
||||
|
||||
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}!");
|
||||
}
|
||||
|
||||
entManager.DeleteEntity(ent);
|
||||
}
|
||||
});
|
||||
|
||||
mapManager.DeleteMap(mapId);
|
||||
});
|
||||
|
||||
await pairTracker.CleanReturnAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task NoStaticPriceAndStackPrice()
|
||||
|
||||
20
Content.Server/Cargo/Components/CargoBountyLabelComponent.cs
Normal file
20
Content.Server/Cargo/Components/CargoBountyLabelComponent.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace Content.Server.Cargo.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for marking containers as
|
||||
/// containing goods for fulfilling bounties.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class CargoBountyLabelComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID for the bounty this label corresponds to.
|
||||
/// </summary>
|
||||
[DataField("id"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Id;
|
||||
|
||||
/// <summary>
|
||||
/// Used to prevent recursion in calculating the price.
|
||||
/// </summary>
|
||||
public bool Calculating;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Content.Shared.Cargo;
|
||||
|
||||
namespace Content.Server.Cargo.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Stores all active cargo bounties for a particular station.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class StationCargoBountyDatabaseComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum amount of bounties a station can have.
|
||||
/// </summary>
|
||||
[DataField("maxBounties"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public int MaxBounties = 3;
|
||||
|
||||
/// <summary>
|
||||
/// A list of all the bounties currently active for a station.
|
||||
/// </summary>
|
||||
[DataField("bounties"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public List<CargoBountyData> Bounties = new();
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine unique order IDs
|
||||
/// </summary>
|
||||
[DataField("totalBounties")]
|
||||
public int TotalBounties;
|
||||
|
||||
/// <summary>
|
||||
/// A poor-man's weighted list of the durations for how long
|
||||
/// each bounty will last.
|
||||
/// </summary>
|
||||
[DataField("bountyDurations")]
|
||||
public List<TimeSpan> BountyDurations = new()
|
||||
{
|
||||
TimeSpan.FromMinutes(5),
|
||||
TimeSpan.FromMinutes(7.5f),
|
||||
TimeSpan.FromMinutes(7.5f),
|
||||
TimeSpan.FromMinutes(7.5f),
|
||||
TimeSpan.FromMinutes(10),
|
||||
TimeSpan.FromMinutes(10),
|
||||
TimeSpan.FromMinutes(10),
|
||||
TimeSpan.FromMinutes(10),
|
||||
TimeSpan.FromMinutes(10),
|
||||
TimeSpan.FromMinutes(15)
|
||||
};
|
||||
}
|
||||
341
Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
Normal file
341
Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
Normal file
@@ -0,0 +1,341 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Labels;
|
||||
using Content.Server.Paper;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Database;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
|
||||
public sealed partial class CargoSystem
|
||||
{
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
|
||||
private void InitializeBounty()
|
||||
{
|
||||
SubscribeLocalEvent<CargoBountyConsoleComponent, BoundUIOpenedEvent>(OnBountyConsoleOpened);
|
||||
SubscribeLocalEvent<CargoBountyConsoleComponent, BountyPrintLabelMessage>(OnPrintLabelMessage);
|
||||
SubscribeLocalEvent<CargoBountyLabelComponent, PriceCalculationEvent>(OnGetBountyPrice);
|
||||
SubscribeLocalEvent<EntitySoldEvent>(OnSold);
|
||||
SubscribeLocalEvent<StationCargoBountyDatabaseComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
private void OnBountyConsoleOpened(EntityUid uid, CargoBountyConsoleComponent component, BoundUIOpenedEvent args)
|
||||
{
|
||||
if (_station.GetOwningStation(uid) is not { } station ||
|
||||
!TryComp<StationCargoBountyDatabaseComponent>(station, out var bountyDb))
|
||||
return;
|
||||
|
||||
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties));
|
||||
}
|
||||
|
||||
private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args)
|
||||
{
|
||||
if (_timing.CurTime < component.NextPrintTime)
|
||||
return;
|
||||
|
||||
if (_station.GetOwningStation(uid) is not { } station)
|
||||
return;
|
||||
|
||||
if (!TryGetBountyFromId(station, args.BountyId, out var bounty))
|
||||
return;
|
||||
|
||||
var label = Spawn(component.BountyLabelId, Transform(uid).Coordinates);
|
||||
component.NextPrintTime = _timing.CurTime + component.PrintDelay;
|
||||
SetupBountyLabel(label, bounty.Value);
|
||||
_audio.PlayPvs(component.PrintSound, uid);
|
||||
}
|
||||
|
||||
public void SetupBountyLabel(EntityUid uid, CargoBountyData bounty, PaperComponent? paper = null, CargoBountyLabelComponent? label = null)
|
||||
{
|
||||
if (!Resolve(uid, ref paper, ref label) || !_protoMan.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var prototype))
|
||||
return;
|
||||
|
||||
label.Id = bounty.Id;
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(Loc.GetString("bounty-manifest-header", ("id", bounty.Id)));
|
||||
msg.PushNewline();
|
||||
msg.AddText(Loc.GetString("bounty-manifest-list-start"));
|
||||
msg.PushNewline();
|
||||
foreach (var entry in prototype.Entries)
|
||||
{
|
||||
msg.AddMarkup($"- {Loc.GetString("bounty-console-manifest-entry",
|
||||
("amount", entry.Amount),
|
||||
("item", Loc.GetString(entry.Name)))}");
|
||||
msg.PushNewline();
|
||||
}
|
||||
_paperSystem.SetContent(uid, msg.ToMarkup(), paper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bounties do not sell for any currency. The reward for a bounty is
|
||||
/// calculated after it is sold separately from the selling system.
|
||||
/// </summary>
|
||||
private void OnGetBountyPrice(EntityUid uid, CargoBountyLabelComponent component, ref PriceCalculationEvent args)
|
||||
{
|
||||
if (args.Handled || component.Calculating)
|
||||
return;
|
||||
|
||||
// make sure this label was actually applied to a crate.
|
||||
if (!_container.TryGetContainingContainer(uid, out var container) || container.ID != LabelSystem.ContainerName)
|
||||
return;
|
||||
|
||||
if (_station.GetOwningStation(uid) is not { } station)
|
||||
return;
|
||||
|
||||
if (!TryGetBountyFromId(station, component.Id, out var bounty))
|
||||
return;
|
||||
|
||||
if (!_protoMan.TryIndex<CargoBountyPrototype>(bounty.Value.Bounty, out var bountyProtoype) ||!IsBountyComplete(container.Owner, bountyProtoype))
|
||||
return;
|
||||
args.Handled = true;
|
||||
|
||||
component.Calculating = true;
|
||||
args.Price = bountyProtoype.Reward - _pricing.GetPrice(container.Owner);
|
||||
component.Calculating = false;
|
||||
}
|
||||
|
||||
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))
|
||||
continue;
|
||||
|
||||
if (!TryGetBountyFromId(args.Station, component.Id, out var bounty))
|
||||
continue;
|
||||
|
||||
if (!IsBountyComplete(container.Owner, bounty.Value))
|
||||
continue;
|
||||
|
||||
TryRemoveBounty(args.Station, bounty.Value);
|
||||
FillBountyDatabase(args.Station);
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"Bounty \"{bounty.Value.Bounty}\" (id:{bounty.Value.Id}) was fulfilled");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, StationCargoBountyDatabaseComponent component, MapInitEvent args)
|
||||
{
|
||||
FillBountyDatabase(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills up the bounty database with random bounties.
|
||||
/// </summary>
|
||||
public void FillBountyDatabase(EntityUid uid, StationCargoBountyDatabaseComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
while (component.Bounties.Count < component.MaxBounties)
|
||||
{
|
||||
if (!TryAddBounty(uid, component))
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateBountyConsoles();
|
||||
}
|
||||
|
||||
public bool IsBountyComplete(EntityUid container, CargoBountyData data)
|
||||
{
|
||||
if (!_protoMan.TryIndex<CargoBountyPrototype>(data.Bounty, out var proto))
|
||||
return false;
|
||||
|
||||
return IsBountyComplete(container, proto.Entries);
|
||||
}
|
||||
|
||||
public bool IsBountyComplete(EntityUid container, string id)
|
||||
{
|
||||
if (!_protoMan.TryIndex<CargoBountyPrototype>(id, out var proto))
|
||||
return false;
|
||||
|
||||
return IsBountyComplete(container, proto.Entries);
|
||||
}
|
||||
|
||||
public bool IsBountyComplete(EntityUid container, CargoBountyPrototype prototype)
|
||||
{
|
||||
return IsBountyComplete(container, prototype.Entries);
|
||||
}
|
||||
|
||||
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(contained, entries);
|
||||
}
|
||||
|
||||
public bool IsBountyComplete(HashSet<EntityUid> entities, IEnumerable<CargoBountyItemEntry> entries)
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var count = 0;
|
||||
|
||||
// store entities that already satisfied an
|
||||
// entry so we don't double-count them.
|
||||
var temp = new HashSet<EntityUid>();
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
if (!entry.Whitelist.IsValid(entity, EntityManager))
|
||||
continue;
|
||||
count++;
|
||||
temp.Add(entity);
|
||||
|
||||
if (count >= entry.Amount)
|
||||
break;
|
||||
}
|
||||
|
||||
if (count < entry.Amount)
|
||||
return false;
|
||||
|
||||
foreach (var ent in temp)
|
||||
{
|
||||
entities.Remove(ent);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool TryAddBounty(EntityUid uid, StationCargoBountyDatabaseComponent? component = null)
|
||||
{
|
||||
// todo: consider making the cargo bounties weighted.
|
||||
var bounty = _random.Pick(_protoMan.EnumeratePrototypes<CargoBountyPrototype>().ToList());
|
||||
return TryAddBounty(uid, bounty, component);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool TryAddBounty(EntityUid uid, string bountyId, StationCargoBountyDatabaseComponent? component = null)
|
||||
{
|
||||
if (!_protoMan.TryIndex<CargoBountyPrototype>(bountyId, out var bounty))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryAddBounty(uid, bounty, component);
|
||||
}
|
||||
|
||||
public bool TryAddBounty(EntityUid uid, CargoBountyPrototype bounty, StationCargoBountyDatabaseComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
if (component.Bounties.Count >= component.MaxBounties)
|
||||
return false;
|
||||
|
||||
var endTime = _timing.CurTime + _random.Pick(component.BountyDurations) + TimeSpan.FromSeconds(_random.Next(-10, 10));
|
||||
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)}");
|
||||
component.TotalBounties++;
|
||||
return true;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool TryRemoveBounty(EntityUid uid, int dataId, StationCargoBountyDatabaseComponent? component = null)
|
||||
{
|
||||
if (!TryGetBountyFromId(uid, dataId, out var data, component))
|
||||
return false;
|
||||
|
||||
return TryRemoveBounty(uid, data.Value, component);
|
||||
}
|
||||
|
||||
public bool TryRemoveBounty(EntityUid uid, CargoBountyData data, StationCargoBountyDatabaseComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < component.Bounties.Count; i++)
|
||||
{
|
||||
if (component.Bounties[i].Id == data.Id)
|
||||
{
|
||||
component.Bounties.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetBountyFromId(
|
||||
EntityUid uid,
|
||||
int id,
|
||||
[NotNullWhen(true)] out CargoBountyData? bounty,
|
||||
StationCargoBountyDatabaseComponent? component = null)
|
||||
{
|
||||
bounty = null;
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
foreach (var bountyData in component.Bounties)
|
||||
{
|
||||
if (bountyData.Id != id)
|
||||
continue;
|
||||
bounty = bountyData;
|
||||
break;
|
||||
}
|
||||
|
||||
return bounty != null;
|
||||
}
|
||||
|
||||
public void UpdateBountyConsoles()
|
||||
{
|
||||
var query = EntityQueryEnumerator<CargoBountyConsoleComponent, ServerUserInterfaceComponent>();
|
||||
while (query.MoveNext(out var uid, out _, out var ui))
|
||||
{
|
||||
if (_station.GetOwningStation(uid) is not { } station ||
|
||||
!TryComp<StationCargoBountyDatabaseComponent>(station, out var db))
|
||||
continue;
|
||||
|
||||
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties), ui: ui);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateBounty()
|
||||
{
|
||||
var query = EntityQueryEnumerator<StationCargoBountyDatabaseComponent>();
|
||||
while (query.MoveNext(out var uid, out var bountyDatabase))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Coordinates;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
|
||||
@@ -228,14 +229,21 @@ public sealed partial class CargoSystem
|
||||
|
||||
#region Station
|
||||
|
||||
private void SellPallets(EntityUid gridUid, out double amount)
|
||||
private void SellPallets(EntityUid gridUid, EntityUid? station, out double amount)
|
||||
{
|
||||
station ??= _station.GetOwningStation(gridUid);
|
||||
GetPalletGoods(gridUid, out var toSell, out amount);
|
||||
|
||||
_sawmill.Debug($"Cargo sold {toSell.Count} entities for {amount}");
|
||||
|
||||
foreach (var ent in toSell)
|
||||
{
|
||||
if (station != null)
|
||||
{
|
||||
var ev = new EntitySoldEvent(station.Value, toSell);
|
||||
RaiseLocalEvent(ref ev);
|
||||
}
|
||||
|
||||
Del(ent);
|
||||
}
|
||||
}
|
||||
@@ -325,7 +333,7 @@ public sealed partial class CargoSystem
|
||||
return;
|
||||
}
|
||||
|
||||
SellPallets(gridUid, out var price);
|
||||
SellPallets(gridUid, null, out var price);
|
||||
var stackPrototype = _protoMan.Index<StackPrototype>(component.CashType);
|
||||
_stack.Spawn((int)price, stackPrototype, uid.ToCoordinates());
|
||||
UpdatePalletConsoleInterface(uid);
|
||||
@@ -359,7 +367,7 @@ public sealed partial class CargoSystem
|
||||
|
||||
if (TryComp<StationBankAccountComponent>(stationUid, out var bank))
|
||||
{
|
||||
SellPallets(uid, out var amount);
|
||||
SellPallets(uid, stationUid, out var amount);
|
||||
bank.Balance += (int) amount;
|
||||
}
|
||||
}
|
||||
@@ -424,3 +432,10 @@ public sealed partial class CargoSystem
|
||||
_console.RefreshShuttleConsoles();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event broadcast raised by-ref before it is sold and
|
||||
/// deleted but after the price has been calculated.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct EntitySoldEvent(EntityUid Station, HashSet<EntityUid> Sold);
|
||||
|
||||
@@ -15,12 +15,14 @@ using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
|
||||
public sealed partial class CargoSystem : SharedCargoSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
@@ -51,6 +53,7 @@ public sealed partial class CargoSystem : SharedCargoSystem
|
||||
InitializeConsole();
|
||||
InitializeShuttle();
|
||||
InitializeTelepad();
|
||||
InitializeBounty();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -64,6 +67,7 @@ public sealed partial class CargoSystem : SharedCargoSystem
|
||||
base.Update(frameTime);
|
||||
UpdateConsole(frameTime);
|
||||
UpdateTelepad(frameTime);
|
||||
UpdateBounty();
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
|
||||
31
Content.Shared/Cargo/CargoBountyData.cs
Normal file
31
Content.Shared/Cargo/CargoBountyData.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Robust.Shared.Serialization;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Cargo;
|
||||
|
||||
/// <summary>
|
||||
/// A data structure for storing currently available bounties.
|
||||
/// </summary>
|
||||
[DataDefinition, NetSerializable, Serializable]
|
||||
public readonly record struct CargoBountyData(int Id, string Bounty, TimeSpan EndTime)
|
||||
{
|
||||
/// <summary>
|
||||
/// A numeric id used to identify the bounty
|
||||
/// </summary>
|
||||
[DataField("id"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public readonly int Id = Id;
|
||||
|
||||
/// <summary>
|
||||
/// The prototype containing information about the bounty.
|
||||
/// </summary>
|
||||
[DataField("bounty", customTypeSerializer: typeof(PrototypeIdSerializer<CargoBountyPrototype>)), ViewVariables(VVAccess.ReadWrite)]
|
||||
public readonly string Bounty = Bounty;
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the bounty is closed and no longer is available.
|
||||
/// </summary>
|
||||
[DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public readonly TimeSpan EndTime = EndTime;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Cargo.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class CargoBountyConsoleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The id of the label entity spawned by the print label button.
|
||||
/// </summary>
|
||||
[DataField("bountyLabelId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string BountyLabelId = "PaperCargoBountyManifest";
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the console will be able to print a label again.
|
||||
/// </summary>
|
||||
[DataField("nextPrintTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan NextPrintTime = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// The time between prints.
|
||||
/// </summary>
|
||||
[DataField("printDelay")]
|
||||
public TimeSpan PrintDelay = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// The sound made when printing occurs
|
||||
/// </summary>
|
||||
[DataField("printSound")]
|
||||
public SoundSpecifier PrintSound = new SoundPathSpecifier("/Audio/Machines/printer.ogg");
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
public sealed class CargoBountyConsoleState : BoundUserInterfaceState
|
||||
{
|
||||
public List<CargoBountyData> Bounties;
|
||||
|
||||
public CargoBountyConsoleState(List<CargoBountyData> bounties)
|
||||
{
|
||||
Bounties = bounties;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BountyPrintLabelMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public int BountyId;
|
||||
|
||||
public BountyPrintLabelMessage(int bountyId)
|
||||
{
|
||||
BountyId = bountyId;
|
||||
}
|
||||
}
|
||||
60
Content.Shared/Cargo/Prototypes/CargoBountyPrototype.cs
Normal file
60
Content.Shared/Cargo/Prototypes/CargoBountyPrototype.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Cargo.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// This is a prototype for a cargo bounty, a set of items
|
||||
/// that must be sold together in a labeled container in order
|
||||
/// to receive a monetary reward.
|
||||
/// </summary>
|
||||
[Prototype("cargoBounty"), Serializable, NetSerializable]
|
||||
public sealed class CargoBountyPrototype : IPrototype
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The monetary reward for completing the bounty
|
||||
/// </summary>
|
||||
[DataField("reward", required: true)]
|
||||
public readonly int Reward;
|
||||
|
||||
/// <summary>
|
||||
/// A description for flava purposes.
|
||||
/// </summary>
|
||||
[DataField("description")]
|
||||
public readonly string Description = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The entries that must be satisfied for the cargo bounty to be complete.
|
||||
/// </summary>
|
||||
[DataField("entries", required: true)]
|
||||
public readonly List<CargoBountyItemEntry> Entries = new();
|
||||
}
|
||||
|
||||
[DataDefinition, Serializable, NetSerializable]
|
||||
public readonly record struct CargoBountyItemEntry()
|
||||
{
|
||||
/// <summary>
|
||||
/// A whitelist for determining what items satisfy the entry.
|
||||
/// </summary>
|
||||
[DataField("whitelist", required: true)]
|
||||
public readonly EntityWhitelist Whitelist = default!;
|
||||
|
||||
// todo: implement some kind of simple generic condition system
|
||||
|
||||
/// <summary>
|
||||
/// How much of the item must be present to satisfy the entry
|
||||
/// </summary>
|
||||
[DataField("amount")]
|
||||
public readonly int Amount = 1;
|
||||
|
||||
/// <summary>
|
||||
/// A player-facing name for the item.
|
||||
/// </summary>
|
||||
[DataField("name")]
|
||||
public readonly string Name = string.Empty;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ namespace Content.Shared.Cargo;
|
||||
public enum CargoConsoleUiKey : byte
|
||||
{
|
||||
Orders,
|
||||
Bounty,
|
||||
Shuttle,
|
||||
Telepad
|
||||
}
|
||||
|
||||
41
Resources/Locale/en-US/cargo/bounties.ftl
Normal file
41
Resources/Locale/en-US/cargo/bounties.ftl
Normal file
@@ -0,0 +1,41 @@
|
||||
bounty-item-artifact = Alien artifact
|
||||
bounty-item-baseball-bat = Baseball bat
|
||||
bounty-item-box-hugs = Box of hugs
|
||||
bounty-item-brain = Brain
|
||||
bounty-item-briefcase = Briefcase
|
||||
bounty-item-carp = Space carp
|
||||
bounty-item-crayon = Crayon
|
||||
bounty-item-donk-pocket = Donk-pocket
|
||||
bounty-item-donut = Donut
|
||||
bounty-item-figurine = Action figure
|
||||
bounty-item-flower = Flower
|
||||
bounty-item-lung = Lung
|
||||
bounty-item-mouse = Dead mouse
|
||||
bounty-item-research-disk = Research disk
|
||||
bounty-item-soap = Soap
|
||||
bounty-item-spear = Spear
|
||||
bounty-item-toolbox = Toolbox
|
||||
bounty-item-tech-disk = Technology disk
|
||||
bounty-item-trash = Trash
|
||||
bounty-item-pen = Pen
|
||||
|
||||
bounty-description-artifact = NanoTrasen is in some hot water for stealing artifacts from non-spacefaring planets. Return one and we'll compensate you for it.
|
||||
bounty-description-baseball-bat = Baseball fever is going on at CentCom! Be a dear and ship them some baseball bats, so that management can live out their childhood dream.
|
||||
bounty-description-box-hugs = Several chief officials have sustained serious boo-boos. A box of hugs is urgently needed to aid in their recovery.
|
||||
bounty-description-brain = Commander Caldwell was rendered brain-dead by a recent space lube accident. Unfortunately, we can't hire a replacement, so just send us a new brain to put in her instead.
|
||||
bounty-description-briefcase = Central Command will be holding a business convention this year. Ship a few briefcases in support.
|
||||
bounty-description-carp = Admiral Pavlov has gone on strike ever since Central Command confiscated her "pet." She is demanding a space carp as a replacement, dead or alive.
|
||||
bounty-description-crayon = Dr Jones' kid ate all our crayons again. Please send us yours.
|
||||
bounty-description-donk-pocket = Consumer safety recall: Warning. Donk-Pockets manufactured in the past year contain hazardous lizard biomatter. Return units to CentCom immediately.
|
||||
bounty-description-donut = CentCom's security forces are facing heavy losses against the Syndicate. Ship donuts to raise morale.
|
||||
bounty-description-figurine = The vice president's son saw an ad for action figures on the telescreen and now he won't shut up about them. Ship some to ease his complaints.
|
||||
bounty-description-flower = Commander Zot really wants to sweep Security Officer Olivia off her feet. Send a shipment of flowers and he'll happily reward you.
|
||||
bounty-description-lung = The pro-smoking league has been fighting to keep cigarettes on our stations for millennia. Unfortunately, they're lungs aren't fighting so hard anymore. Send them some new ones.
|
||||
bounty-description-mouse = Station 13 ran out of freeze-dried mice. Ship some fresh ones so their janitor doesn't go on strike.
|
||||
bounty-description-research-disk = Turns out those bozos in the Research department have been spending all their time getting janitorial equipment. Send some research up to Central Command so we can actually get what we need.
|
||||
bounty-description-soap = Soap has gone missing from CentCom's bathrooms and nobody knows who took it. Replace it and be the hero CentCom needs.
|
||||
bounty-description-spear = CentCom's security forces are going through budget cuts. You will be paid if you ship a set of spears.
|
||||
bounty-description-toolbox = There's an absence of robustness at Central Command. Hurry up and ship some toolboxes as a solution.
|
||||
bounty-description-tech-disk = The new research assistant on Station 13 spilled a soda on the RND server. Send them some technology disks so they can build up their recipes.
|
||||
bounty-description-trash = Recently a group of janitors have run out of trash to clean up, without any trash Centcom wants to fire them to cut costs. Send a shipment of trash to keep them employed, and they'll give you a small compensation.
|
||||
bounty-description-pen = We are hosting the intergalactic pen balancing competition. We need you to send us some standardized ball point pens.
|
||||
18
Resources/Locale/en-US/cargo/cargo-bounty-console.ftl
Normal file
18
Resources/Locale/en-US/cargo/cargo-bounty-console.ftl
Normal file
@@ -0,0 +1,18 @@
|
||||
bounty-console-menu-title = Cargo bounty console
|
||||
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-entry =
|
||||
{ $amount ->
|
||||
[1] {$item}
|
||||
*[other] {$item} x{$amount}
|
||||
}
|
||||
bounty-console-description-label = [color=gray]{$description}[/color]
|
||||
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-list-start = Item manifest:
|
||||
@@ -13,6 +13,7 @@ guide-entry-controls = Controls
|
||||
guide-entry-radio = Radio
|
||||
guide-entry-jobs = Jobs
|
||||
guide-entry-cargo = Cargo
|
||||
guide-entry-cargo-bounties = Cargo Bounties
|
||||
guide-entry-salvage = Salvage
|
||||
guide-entry-survival = Survival
|
||||
guide-entry-chemicals = Chemicals
|
||||
|
||||
219
Resources/Prototypes/Catalog/Bounties/bounties.yml
Normal file
219
Resources/Prototypes/Catalog/Bounties/bounties.yml
Normal file
@@ -0,0 +1,219 @@
|
||||
- type: cargoBounty
|
||||
id: BountyArtifact
|
||||
reward: 2500
|
||||
description: bounty-description-artifact
|
||||
entries:
|
||||
- name: bounty-item-artifact
|
||||
amount: 1
|
||||
whitelist:
|
||||
components:
|
||||
- Artifact
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyBaseballBat
|
||||
reward: 800
|
||||
description: bounty-description-baseball-bat
|
||||
entries:
|
||||
- name: bounty-item-baseball-bat
|
||||
amount: 5
|
||||
whitelist:
|
||||
tags:
|
||||
- BaseballBat
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyBoxHug
|
||||
reward: 600
|
||||
description: bounty-description-box-hugs
|
||||
entries:
|
||||
- name: bounty-item-box-hugs
|
||||
amount: 1
|
||||
whitelist:
|
||||
tags:
|
||||
- BoxHug
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyBrain
|
||||
reward: 2000
|
||||
description: bounty-description-brain
|
||||
entries:
|
||||
- name: bounty-item-brain
|
||||
amount: 1
|
||||
whitelist:
|
||||
components:
|
||||
- Brain
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyBriefcase
|
||||
reward: 1000
|
||||
description: bounty-description-briefcase
|
||||
entries:
|
||||
- name: bounty-item-briefcase
|
||||
amount: 5
|
||||
whitelist:
|
||||
tags:
|
||||
- Briefcase
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyCarp
|
||||
reward: 2000
|
||||
description: bounty-description-carp
|
||||
entries:
|
||||
- name: bounty-item-carp
|
||||
amount: 1
|
||||
whitelist:
|
||||
tags:
|
||||
- Carp
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyCrayon
|
||||
reward: 800
|
||||
description: bounty-description-crayon
|
||||
entries:
|
||||
- name: bounty-item-crayon
|
||||
amount: 24
|
||||
whitelist:
|
||||
tags:
|
||||
- Crayon
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyDonkPocket
|
||||
reward: 1200
|
||||
description: bounty-description-donk-pocket
|
||||
entries:
|
||||
- name: bounty-item-donk-pocket
|
||||
amount: 12
|
||||
whitelist:
|
||||
tags:
|
||||
- DonkPocket
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyDonut
|
||||
reward: 1200
|
||||
description: bounty-description-donut
|
||||
entries:
|
||||
- name: bounty-item-donut
|
||||
amount: 10
|
||||
whitelist:
|
||||
tags:
|
||||
- Donut
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyFigurine
|
||||
reward: 1600
|
||||
description: bounty-description-figurine
|
||||
entries:
|
||||
- name: bounty-item-figurine
|
||||
amount: 5
|
||||
whitelist:
|
||||
tags:
|
||||
- Figurine
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyFlower
|
||||
reward: 400
|
||||
description: bounty-description-flower
|
||||
entries:
|
||||
- name: bounty-item-flower
|
||||
amount: 3
|
||||
whitelist:
|
||||
tags:
|
||||
- Flower
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyLung
|
||||
reward: 3000
|
||||
description: bounty-description-lung
|
||||
entries:
|
||||
- name: bounty-item-lung
|
||||
amount: 3
|
||||
whitelist:
|
||||
components:
|
||||
- Lung
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyMouse
|
||||
reward: 600
|
||||
description: bounty-description-mouse
|
||||
entries:
|
||||
- name: bounty-item-mouse
|
||||
amount: 5
|
||||
whitelist:
|
||||
tags:
|
||||
- Mouse
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyResearchDisk
|
||||
reward: 1200
|
||||
description: bounty-description-research-disk
|
||||
entries:
|
||||
- name: bounty-item-research-disk
|
||||
amount: 1
|
||||
whitelist:
|
||||
components:
|
||||
- ResearchDisk
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountySoap
|
||||
reward: 800
|
||||
description: bounty-description-soap
|
||||
entries:
|
||||
- name: bounty-item-soap
|
||||
amount: 3
|
||||
whitelist:
|
||||
tags:
|
||||
- Soap
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountySpear
|
||||
reward: 800
|
||||
description: bounty-description-spear
|
||||
entries:
|
||||
- name: bounty-item-spear
|
||||
amount: 5
|
||||
whitelist:
|
||||
tags:
|
||||
- Spear
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyTechDisk
|
||||
reward: 2000
|
||||
description: bounty-description-tech-disk
|
||||
entries:
|
||||
- name: bounty-item-tech-disk
|
||||
amount: 10
|
||||
whitelist:
|
||||
components:
|
||||
- TechnologyDisk
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyToolbox
|
||||
reward: 800
|
||||
description: bounty-description-toolbox
|
||||
entries:
|
||||
- name: bounty-item-toolbox
|
||||
amount: 6
|
||||
whitelist:
|
||||
tags:
|
||||
- Toolbox
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyTrash
|
||||
reward: 400
|
||||
description: bounty-description-trash
|
||||
entries:
|
||||
- name: bounty-item-trash
|
||||
amount: 10
|
||||
whitelist:
|
||||
tags:
|
||||
- Trash
|
||||
|
||||
- type: cargoBounty
|
||||
id: BountyPen
|
||||
reward: 800
|
||||
description: bounty-description-pen
|
||||
entries:
|
||||
- name: bounty-item-pen
|
||||
amount: 10
|
||||
whitelist:
|
||||
tags:
|
||||
- Write
|
||||
@@ -20,6 +20,7 @@
|
||||
- id: CargoShuttleComputerCircuitboard
|
||||
- id: CargoShuttleConsoleCircuitboard
|
||||
- id: SalvageShuttleConsoleCircuitboard
|
||||
- id: CargoBountyComputerCircuitboard
|
||||
- id: CigPackGreen
|
||||
prob: 0.50
|
||||
- id: DoorRemoteCargo
|
||||
|
||||
@@ -908,6 +908,7 @@
|
||||
tags:
|
||||
- Trash
|
||||
- CannotSuicide
|
||||
- Mouse
|
||||
- type: Respirator
|
||||
damage:
|
||||
types:
|
||||
|
||||
@@ -78,6 +78,21 @@
|
||||
- DroneUsable
|
||||
- HighRiskItem
|
||||
|
||||
- type: entity
|
||||
id: CargoBountyComputerCircuitboard
|
||||
parent: BaseComputerCircuitboard
|
||||
name: cargo bounty computer board
|
||||
description: A computer printed circuit board for a cargo bounty computer.
|
||||
components:
|
||||
- type: Sprite
|
||||
state: cpu_supply
|
||||
- type: ComputerBoard
|
||||
prototype: ComputerCargoBounty
|
||||
- type: StaticPrice
|
||||
- type: Tag
|
||||
tags:
|
||||
- DroneUsable
|
||||
|
||||
- type: entity
|
||||
parent: BaseComputerCircuitboard
|
||||
id: CargoShuttleComputerCircuitboard
|
||||
|
||||
@@ -475,6 +475,9 @@
|
||||
Plastic: 100
|
||||
- type: StaticPrice
|
||||
price: 10
|
||||
- type: Tag
|
||||
tags:
|
||||
- Figurine
|
||||
|
||||
- type: entity
|
||||
parent: BaseFigurine
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
size: 60
|
||||
- type: Storage
|
||||
capacity: 60
|
||||
- type: Tag
|
||||
tags:
|
||||
- Briefcase
|
||||
|
||||
- type: entity
|
||||
name: brown briefcase
|
||||
|
||||
@@ -127,6 +127,19 @@
|
||||
headerImagePath: "/Textures/Interface/Paper/paper_heading_cargo_invoice.svg.96dpi.png"
|
||||
headerMargin: 0.0, 12.0, 0.0, 0.0
|
||||
|
||||
- type: entity
|
||||
id: PaperCargoBountyManifest
|
||||
parent: PaperCargoInvoice
|
||||
name: bounty manifest
|
||||
description: A paper label designating a crate as containing a bounty. Selling a crate with this label will fulfill the bounty.
|
||||
components:
|
||||
- type: CargoBountyLabel
|
||||
- type: StaticPrice
|
||||
price: 0
|
||||
- type: GuideHelp
|
||||
guides:
|
||||
- CargoBounties
|
||||
|
||||
- type: entity
|
||||
parent: Paper
|
||||
id: PaperWritten
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- DroneUsable
|
||||
- Toolbox
|
||||
|
||||
- type: entity
|
||||
name: emergency toolbox
|
||||
|
||||
@@ -25,3 +25,6 @@
|
||||
- type: Construction
|
||||
graph: WoodenBat
|
||||
node: bat
|
||||
- type: Tag
|
||||
tags:
|
||||
- BaseballBat
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
components:
|
||||
- type: StationBankAccount
|
||||
- type: StationCargoOrderDatabase
|
||||
- type: StationCargoBountyDatabase
|
||||
|
||||
- type: entity
|
||||
id: BaseStationJobsSpawning
|
||||
|
||||
@@ -713,6 +713,39 @@
|
||||
ports:
|
||||
- OrderSender
|
||||
|
||||
- type: entity
|
||||
id: ComputerCargoBounty
|
||||
parent: BaseComputer
|
||||
name: cargo bounty computer
|
||||
description: Used to manage currently active bounties.
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- map: ["computerLayerBody"]
|
||||
state: computer
|
||||
- map: ["computerLayerKeyboard"]
|
||||
state: generic_keyboard
|
||||
- map: ["computerLayerScreen"]
|
||||
state: bounty
|
||||
- map: ["computerLayerKeys"]
|
||||
state: tech_key
|
||||
- type: CargoBountyConsole
|
||||
- type: ActivatableUI
|
||||
key: enum.CargoConsoleUiKey.Bounty
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.CargoConsoleUiKey.Bounty
|
||||
type: CargoBountyConsoleBoundUserInterface
|
||||
- type: Computer
|
||||
board: CargoBountyComputerCircuitboard
|
||||
- type: PointLight
|
||||
radius: 1.5
|
||||
energy: 1.6
|
||||
color: "#b89f25"
|
||||
- type: GuideHelp
|
||||
guides:
|
||||
- CargoBounties
|
||||
|
||||
- type: entity
|
||||
parent: BaseComputer
|
||||
id: ComputerCloningConsole
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
- type: guideEntry
|
||||
id: Cargo
|
||||
name: guide-entry-cargo
|
||||
text: "/ServerInfo/Guidebook/Cargo.xml"
|
||||
text: "/ServerInfo/Guidebook/Cargo/Cargo.xml"
|
||||
children:
|
||||
- CargoBounties
|
||||
|
||||
- type: guideEntry
|
||||
id: CargoBounties
|
||||
name: guide-entry-cargo-bounties
|
||||
text: "/ServerInfo/Guidebook/Cargo/CargoBounties.xml"
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
- type: Tag
|
||||
id: Baguette
|
||||
|
||||
- type: Tag
|
||||
id: BaseballBat
|
||||
|
||||
- type: Tag
|
||||
id: Bee
|
||||
|
||||
@@ -67,6 +70,9 @@
|
||||
- type: Tag
|
||||
id: BrassInstrument
|
||||
|
||||
- type: Tag
|
||||
id: Briefcase
|
||||
|
||||
- type: Tag
|
||||
id: Brutepack
|
||||
|
||||
@@ -260,6 +266,9 @@
|
||||
- type: Tag
|
||||
id: ExplosivePassable
|
||||
|
||||
- type: Tag
|
||||
id: Figurine
|
||||
|
||||
- type: Tag
|
||||
id: FireAlarm
|
||||
|
||||
@@ -529,6 +538,9 @@
|
||||
- type: Tag
|
||||
id: Mop
|
||||
|
||||
- type: Tag
|
||||
id: Mouse
|
||||
|
||||
- type: Tag
|
||||
id: Multitool
|
||||
|
||||
@@ -740,6 +752,9 @@
|
||||
- type: Tag
|
||||
id: TimerSignalElectronics
|
||||
|
||||
- type: Tag
|
||||
id: Toolbox
|
||||
|
||||
- type: Tag
|
||||
id: Trash
|
||||
|
||||
|
||||
@@ -26,6 +26,6 @@
|
||||
<GuideEntityEmbed Entity="AppraisalTool"/>
|
||||
<GuideEntityEmbed Entity="CargoPallet"/>
|
||||
</Box>
|
||||
After finding something worth selling, place it on one of the shuttle's cargo pallets. The next time the shuttle is sent to a trading post, the item will be sold and the money will be directly transferred back to the station's bank account.
|
||||
After finding something worth selling, place it on one of the shuttle's cargo pallets. The next time the shuttle is sent to a trading post, the item will be sold and the money will be directly transferred back to the station's bank account. You can also make even more money by completing [textlink="bounties" link="CargoBounties"] or selling valuable items from [textlink="salvage" link="Salvage"].
|
||||
|
||||
</Document>
|
||||
29
Resources/ServerInfo/Guidebook/Cargo/CargoBounties.xml
Normal file
29
Resources/ServerInfo/Guidebook/Cargo/CargoBounties.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<Document>
|
||||
# Cargo Bounties
|
||||
[textlink="Cargo" link="Cargo"] can always make money selling items whenever they feel like it. However, there are ways to make even more money by being more selective about what and when you're selling. One of these ways is [color=#a4885c]bounties[/color].
|
||||
|
||||
<Box>
|
||||
<GuideEntityEmbed Entity="ComputerCargoBounty"/>
|
||||
</Box>
|
||||
Every station has a fixed amount of bounties that are globally available. These can be checked at the [color=#a4885c]cargo bounty computer[/color], which shows the following information about them:
|
||||
- What items are needed to fulfill the bounty
|
||||
- How much money do you get for completing the bounty
|
||||
- How much time is left before the bounty expires
|
||||
|
||||
Assuming that you are able to aquire all of the items for it in a time efficient and low-cost manner, you will be able to complete the bounty and get a significantly higher payout than you would be selling the items conventionally.
|
||||
|
||||
## Completing Bounites
|
||||
<Box>
|
||||
<GuideEntityEmbed Entity="PaperCargoBountyManifest"/>
|
||||
<GuideEntityEmbed Entity="CrateGenericSteel"/>
|
||||
<GuideEntityEmbed Entity="CargoPallet"/>
|
||||
</Box>
|
||||
Once you have collected the items you need for a bounty, completing it is a simple process:
|
||||
- Place all of the items inside of the crate and [bold]close it![/bold]
|
||||
- Get the bounties manifest label by clicking the [color=#a4885c]print label[/color] button on corresponding entry on the cargo bounty computer.
|
||||
- Place the label on the crate containing the items.
|
||||
- Move the crate onto the cargo shuttle.
|
||||
- Sell it.
|
||||
|
||||
And there you go! After you follow these steps, the bounty should disappear from the computer and a new one will take its place.
|
||||
</Document>
|
||||
BIN
Resources/Textures/Structures/Machines/computers.rsi/bounty.png
Normal file
BIN
Resources/Textures/Structures/Machines/computers.rsi/bounty.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
@@ -203,6 +203,44 @@
|
||||
"name": "atmos_key_off",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "bounty",
|
||||
"directions": 4,
|
||||
"delays": [
|
||||
[
|
||||
0.3,
|
||||
0.3,
|
||||
0.3,
|
||||
0.3,
|
||||
0.3,
|
||||
0.3
|
||||
],
|
||||
[
|
||||
0.3,
|
||||
0.3,
|
||||
0.3,
|
||||
0.3,
|
||||
0.3,
|
||||
0.3
|
||||
],
|
||||
[
|
||||
0.3,
|
||||
0.3,
|
||||
0.3,
|
||||
0.3,
|
||||
0.3,
|
||||
0.3
|
||||
],
|
||||
[
|
||||
0.3,
|
||||
0.3,
|
||||
0.3,
|
||||
0.3,
|
||||
0.3,
|
||||
0.3
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "broken",
|
||||
"directions": 4
|
||||
|
||||
Reference in New Issue
Block a user