Salvage Job Board (#37549)

* Salvage Job Board

* More development

* Small boy

* Computer yaml (partial)

* UI

* Rank unlock logic

* Job label printing

* appraisal tool integration

* Jobs

* add board to QM locker

* boom!

* command desc

* mild rewording

* ackh, mein pr ist brohken
This commit is contained in:
Nemanja
2025-05-18 00:02:52 -04:00
committed by GitHub
parent 93305c21df
commit 0d878751fa
45 changed files with 1239 additions and 61 deletions

View File

@@ -138,6 +138,11 @@ namespace Content.Client.Cargo.BUI
AccountName = cState.Name;
if (_menu == null)
return;
_menu.ProductCatalogue = cState.Products;
_menu?.UpdateStation(station);
Populate(cState.Orders);
}

View File

@@ -40,6 +40,8 @@ namespace Content.Client.Cargo.UI
private readonly List<string> _categoryStrings = new();
private string? _category;
public List<ProtoId<CargoProductPrototype>> ProductCatalogue = new();
public CargoConsoleMenu(EntityUid owner, IEntityManager entMan, IPrototypeManager protoManager, SpriteSystem spriteSystem)
{
RobustXamlLoader.Load(this);
@@ -113,14 +115,16 @@ namespace Content.Client.Cargo.UI
Categories.SelectId(id);
}
public IEnumerable<CargoProductPrototype> ProductPrototypes
private IEnumerable<CargoProductPrototype> ProductPrototypes
{
get
{
var allowedGroups = _entityManager.GetComponentOrNull<CargoOrderConsoleComponent>(_owner)?.AllowedGroups;
foreach (var cargoPrototype in _protoManager.EnumeratePrototypes<CargoProductPrototype>())
foreach (var cargoPrototypeId in ProductCatalogue)
{
var cargoPrototype = _protoManager.Index(cargoPrototypeId);
if (!allowedGroups?.Contains(cargoPrototype.Group) ?? false)
continue;

View File

@@ -0,0 +1,29 @@
<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="Horizontal" HorizontalExpand="True">
<BoxContainer HorizontalExpand="True" Orientation="Vertical" MaxWidth="380">
<RichTextLabel Name="NameLabel" StyleClasses="LabelKeyText"/>
<customControls:HSeparator Margin="0 0 0 5"/>
<RichTextLabel Name="DescriptionLabel" HorizontalExpand="True"/>
<Control MinHeight="10"/>
<RichTextLabel Name="ManifestLabel"/>
<Control MinHeight="10"/>
<RichTextLabel Name="RewardLabel" VerticalExpand="True" VerticalAlignment="Bottom"/>
</BoxContainer>
<BoxContainer HorizontalExpand="True" VerticalExpand="True" VerticalAlignment="Center">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" HorizontalAlignment="Center">
<TextureRect Name="IconRect" MinSize="64 64" HorizontalAlignment="Center"/>
<Control MinHeight="20"/>
<Button Name="PrintButton"
Text="{Loc 'bounty-console-label-button-text'}"
HorizontalExpand="False"
HorizontalAlignment="Center"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</PanelContainer>
</BoxContainer>

View File

@@ -0,0 +1,44 @@
using System.Numerics;
using Content.Client.Message;
using Content.Shared.Cargo.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Salvage.UI;
[GenerateTypedNameReferences]
public sealed partial class JobEntry : BoxContainer
{
public Action? OnLabelButtonPressed;
public JobEntry(CargoBountyPrototype job, IEntityManager entMan)
{
RobustXamlLoader.Load(this);
NameLabel.SetMarkup(Loc.GetString($"salv-job-board-name-{job.ID}"));
var items = new List<string>();
foreach (var entry in job.Entries)
{
items.Add(Loc.GetString("bounty-console-manifest-entry",
("amount", entry.Amount),
("item", Loc.GetString(entry.Name))));
}
ManifestLabel.SetMarkup(Loc.GetString("job-board-ui-label-items", ("item", string.Join(", ", items))));
RewardLabel.SetMarkup(Loc.GetString("bounty-console-reward-label", ("reward", job.Reward)));
DescriptionLabel.SetMarkup(Loc.GetString(job.Description));
if (job.Sprite != null)
{
var texture = entMan.System<SpriteSystem>().Frame0(job.Sprite);
// Make sure the actual size of the control is the same regardless of the texture size.
IconRect.TextureScale = Vector2.One * (3f / (texture.Size.X / 32f));
IconRect.Texture = texture;
}
PrintButton.OnPressed += _ => OnLabelButtonPressed?.Invoke();
}
}

View File

@@ -0,0 +1,35 @@
using Content.Shared.Cargo.Components;
using Content.Shared.Salvage.JobBoard;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client.Salvage.UI;
[UsedImplicitly]
public sealed class SalvageJobBoardBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
[ViewVariables]
private SalvageJobBoardMenu? _menu;
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<SalvageJobBoardMenu>();
_menu.OnLabelButtonPressed += id =>
{
SendMessage(new JobBoardPrintLabelMessage(id));
};
}
protected override void UpdateState(BoundUserInterfaceState message)
{
base.UpdateState(message);
if (message is not SalvageJobBoardConsoleState state)
return;
_menu?.Update(state);
}
}

View File

@@ -0,0 +1,45 @@
<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 'job-board-ui-window-title'}"
SetSize="550 550"
Resizable="False">
<BoxContainer Orientation="Vertical"
VerticalExpand="True"
HorizontalExpand="True">
<BoxContainer Orientation="Horizontal" Margin="20 10 20 0">
<ProgressBar Name="RankProgressBar" HorizontalExpand="True" MinValue="0" MaxValue="1">
<ProgressBar.ForegroundStyleBoxOverride>
<gfx:StyleBoxFlat BackgroundColor="#FFFF00"/>
</ProgressBar.ForegroundStyleBoxOverride>
</ProgressBar>
<Control MinWidth="20"/>
<RichTextLabel Text="{Loc 'job-board-ui-label-rank'}" Margin="0 0 5 0"/>
<RichTextLabel Text="{Loc 'salvage-job-rank-title-0'}" Name="RankLabel"/>
</BoxContainer>
<PanelContainer VerticalExpand="True" HorizontalExpand="True" Margin="10">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<ScrollContainer HScrollEnabled="False"
HorizontalExpand="True"
VerticalExpand="True">
<BoxContainer Name="CurrentJobContainer"
Orientation="Vertical"
VerticalExpand="True"
HorizontalExpand="True"
Margin="0 0 0 10"/>
</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-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>

View File

@@ -0,0 +1,38 @@
using Content.Client.Message;
using Content.Client.UserInterface.Controls;
using Content.Shared.Salvage.JobBoard;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.Salvage.UI;
[GenerateTypedNameReferences]
public sealed partial class SalvageJobBoardMenu : FancyWindow
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public Action<string>? OnLabelButtonPressed;
public SalvageJobBoardMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
public void Update(SalvageJobBoardConsoleState state)
{
RankLabel.SetMarkup(Loc.GetString(state.Title));
RankProgressBar.Value = state.Progression;
CurrentJobContainer.Children.Clear();
foreach (var job in state.AvailableJobs)
{
var entry = new JobEntry(_prototypeManager.Index(job), _entityManager);
entry.OnLabelButtonPressed += () => OnLabelButtonPressed?.Invoke(job);
CurrentJobContainer.AddChild(entry);
}
}
}

View File

@@ -1,4 +1,6 @@
using Content.Shared.Cargo;
using Content.Shared.Cargo.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Cargo.Components;
@@ -41,6 +43,12 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
[DataField]
public HashSet<string> CheckedBounties = new();
/// <summary>
/// The group that bounties are pulled from.
/// </summary>
[DataField]
public ProtoId<CargoBountyGroupPrototype> Group = "StationBounty";
/// <summary>
/// The time at which players will be able to skip the next bounty.
/// </summary>

View File

@@ -31,6 +31,16 @@ public sealed partial class StationCargoOrderDatabaseComponent : Component
[ViewVariables]
public int NumOrdersCreated;
/// <summary>
/// An all encompassing determiner of what markets can be ordered from.
/// Not every console can order from every market, but a console can't order from a market not on this list.
/// </summary>
[DataField]
public List<ProtoId<CargoMarketPrototype>> Markets = new()
{
"market",
};
// TODO: Can probably dump this
/// <summary>
/// The cargo shuttle assigned to this station.

View File

@@ -16,6 +16,7 @@ using Content.Shared.Whitelist;
using JetBrains.Annotations;
using Robust.Server.Containers;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -292,6 +293,13 @@ public sealed partial class CargoSystem
return IsBountyComplete(container, proto.Entries);
}
public bool IsBountyComplete(EntityUid container, ProtoId<CargoBountyPrototype> prototypeId)
{
var prototype = _protoMan.Index(prototypeId);
return IsBountyComplete(container, prototype.Entries);
}
public bool IsBountyComplete(EntityUid container, CargoBountyPrototype prototype)
{
return IsBountyComplete(container, prototype.Entries);
@@ -392,7 +400,9 @@ public sealed partial class CargoSystem
return false;
// todo: consider making the cargo bounties weighted.
var allBounties = _protoMan.EnumeratePrototypes<CargoBountyPrototype>().ToList();
var allBounties = _protoMan.EnumeratePrototypes<CargoBountyPrototype>()
.Where(p => p.Group == component.Group)
.ToList();
var filteredBounties = new List<CargoBountyPrototype>();
foreach (var proto in allBounties)
{

View File

@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Cargo.Components;
using Content.Server.Station.Components;
using Content.Shared.Cargo;
@@ -372,7 +373,7 @@ namespace Content.Server.Cargo.Systems
return;
}
if (!component.AllowedGroups.Contains(product.Group))
if (!GetAvailableProducts((uid, component)).Contains(args.CargoProductId))
return;
if (component.SlipPrinter)
@@ -421,7 +422,8 @@ namespace Content.Server.Cargo.Systems
GetOutstandingOrderCount(orderDatabase, console.Account),
orderDatabase.Capacity,
GetNetEntity(station.Value),
orderDatabase.Orders[console.Account]
orderDatabase.Orders[console.Account],
GetAvailableProducts((consoleUid, console))
));
}
}
@@ -617,6 +619,29 @@ namespace Content.Server.Cargo.Systems
}
public List<ProtoId<CargoProductPrototype>> GetAvailableProducts(Entity<CargoOrderConsoleComponent> ent)
{
if (_station.GetOwningStation(ent) is not { } station ||
!TryComp<StationCargoOrderDatabaseComponent>(station, out var db))
{
return new List<ProtoId<CargoProductPrototype>>();
}
var products = new List<ProtoId<CargoProductPrototype>>();
// Note that a market must be both on the station and on the console to be available.
var markets = ent.Comp.AllowedGroups.Intersect(db.Markets).ToList();
foreach (var product in _protoMan.EnumeratePrototypes<CargoProductPrototype>())
{
if (!markets.Contains(product.Group))
continue;
products.Add(product.ID);
}
return products;
}
#region Station
private bool TryGetOrderDatabase([NotNullWhen(true)] EntityUid? stationUid, [MaybeNullWhen(false)] out StationCargoOrderDatabaseComponent dbComp)

View File

@@ -131,14 +131,14 @@ public sealed partial class CargoSystem
#region Station
private bool SellPallets(EntityUid gridUid, out HashSet<(EntityUid, OverrideSellComponent?, double)> goods)
private bool SellPallets(EntityUid gridUid, EntityUid station, out HashSet<(EntityUid, OverrideSellComponent?, double)> goods)
{
GetPalletGoods(gridUid, out var toSell, out goods);
if (toSell.Count == 0)
return false;
var ev = new EntitySoldEvent(toSell);
var ev = new EntitySoldEvent(toSell, station);
RaiseLocalEvent(ref ev);
foreach (var ent in toSell)
@@ -230,7 +230,7 @@ public sealed partial class CargoSystem
return;
}
if (!SellPallets(gridUid, out var goods))
if (!SellPallets(gridUid, station, out var goods))
return;
var baseDistribution = CreateAccountDistribution((station, bankAccount));
@@ -267,4 +267,4 @@ public sealed partial class CargoSystem
/// deleted but after the price has been calculated.
/// </summary>
[ByRefEvent]
public readonly record struct EntitySoldEvent(HashSet<EntityUid> Sold);
public readonly record struct EntitySoldEvent(HashSet<EntityUid> Sold, EntityUid Station);

View File

@@ -1,4 +1,5 @@
using Content.Server.Popups;
using Content.Server.Salvage.JobBoard;
using Content.Shared.Cargo.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Timing;
@@ -13,6 +14,7 @@ public sealed class PriceGunSystem : SharedPriceGunSystem
[Dependency] private readonly PricingSystem _pricingSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly CargoSystem _bountySystem = default!;
[Dependency] private readonly SalvageJobBoardSystem _salvageJobBoard = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
protected override bool GetPriceOrBounty(Entity<PriceGunComponent> entity, EntityUid target, EntityUid user)
@@ -24,6 +26,10 @@ public sealed class PriceGunSystem : SharedPriceGunSystem
{
_popupSystem.PopupEntity(Loc.GetString("price-gun-bounty-complete"), user, user);
}
else if (_salvageJobBoard.FulfillsSalvageJob(target, null, out _))
{
_popupSystem.PopupEntity(Loc.GetString("price-gun-salvjob-complete"), user, user);
}
else // Otherwise appraise the price
{
var price = _pricingSystem.GetPrice(target);

View File

@@ -0,0 +1,22 @@
using Content.Server.Administration;
using Content.Shared.Administration;
using Content.Shared.Cargo.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Toolshed;
namespace Content.Server.Salvage.JobBoard;
[ToolshedCommand, AdminCommand(AdminFlags.Debug)]
public sealed class JobBoardCommand : ToolshedCommand
{
/// <summary> Completes a bounty automatically. </summary>
[CommandImplementation("completeJob")]
public void CompleteJob([PipedArgument] EntityUid station, ProtoId<CargoBountyPrototype> job)
{
if (!TryComp<SalvageJobsDataComponent>(station, out var salvageJobData))
return;
var sys = EntityManager.System<SalvageJobBoardSystem>();
sys.TryCompleteSalvageJob((station, salvageJobData), job);
}
}

View File

@@ -0,0 +1,17 @@
using Content.Shared.Cargo.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Server.Salvage.JobBoard;
/// <summary>
/// Marks a label for a bounty for a given salvage job board prototype.
/// </summary>
[RegisterComponent]
public sealed partial class JobBoardLabelComponent : Component
{
/// <summary>
/// The bounty corresponding to this label.
/// </summary>
[DataField]
public ProtoId<CargoBountyPrototype>? JobId;
}

View File

@@ -0,0 +1,294 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Cargo.Components;
using Content.Server.Cargo.Systems;
using Content.Server.Radio.EntitySystems;
using Content.Server.Station.Systems;
using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Prototypes;
using Content.Shared.Labels.EntitySystems;
using Content.Shared.Paper;
using Content.Shared.Radio;
using Content.Shared.Salvage.JobBoard;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Salvage.JobBoard;
public sealed class SalvageJobBoardSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly CargoSystem _cargo = default!;
[Dependency] private readonly LabelSystem _label = default!;
[Dependency] private readonly PaperSystem _paper = default!;
[Dependency] private readonly RadioSystem _radio = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
/// <summary>
/// Radio channel that unlock messages are broadcast on.
/// </summary>
private static readonly ProtoId<RadioChannelPrototype> UnlockChannel = "Supply";
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<EntitySoldEvent>(OnSold);
SubscribeLocalEvent<SalvageJobBoardConsoleComponent, BoundUIOpenedEvent>(OnBUIOpened);
Subs.BuiEvents<SalvageJobBoardConsoleComponent>(SalvageJobBoardUiKey.Key,
subs =>
{
subs.Event<JobBoardPrintLabelMessage>(OnPrintLabelMessage);
});
}
private void OnSold(ref EntitySoldEvent args)
{
if (!TryComp<SalvageJobsDataComponent>(args.Station, out var salvageJobsData))
return;
foreach (var sold in args.Sold)
{
if (!FulfillsSalvageJob(sold, (args.Station, salvageJobsData), out var jobId))
return;
TryCompleteSalvageJob((args.Station, salvageJobsData), jobId.Value);
}
}
/// <summary>
/// Gets the jobs that the station can currently access.
/// </summary>
public List<ProtoId<CargoBountyPrototype>> GetAvailableJobs(Entity<SalvageJobsDataComponent> ent)
{
var outJobs = new List<ProtoId<CargoBountyPrototype>>();
var availableGroups = new HashSet<ProtoId<CargoBountyGroupPrototype>>();
var completedCount = ent.Comp.CompletedJobs.Count;
foreach (var (thresholds, rank) in ent.Comp.RankThresholds)
{
if (completedCount < thresholds)
continue;
if (rank.BountyGroup == null)
continue;
availableGroups.Add(rank.BountyGroup.Value);
}
foreach (var bounty in _prototypeManager.EnumeratePrototypes<CargoBountyPrototype>())
{
if (ent.Comp.CompletedJobs.Contains(bounty))
continue;
if (availableGroups.Contains(bounty.Group))
outJobs.Add(bounty);
}
return outJobs;
}
/// <summary>
/// Gets the "progression" of a rank, expressed as on the range [0, 1]
/// </summary>
public float GetRankProgression(Entity<SalvageJobsDataComponent> ent)
{
// Need to have at least two of these.
if (ent.Comp.RankThresholds.Count <= 1)
return 1;
var completedCount = ent.Comp.CompletedJobs.Count;
for (var i = ent.Comp.RankThresholds.Count - 1; i >= 0; i--)
{
var low = ent.Comp.RankThresholds.Keys.ElementAt(i);
if (completedCount < low)
continue;
// don't worry abooouuuuut it (it'll be O K !)
var high = i != ent.Comp.RankThresholds.Count - 1
? ent.Comp.RankThresholds.Keys.ElementAt(i + 1)
: _prototypeManager.EnumeratePrototypes<CargoBountyPrototype>()
.Count(p => ent.Comp.RankThresholds.Values
.Select(r => r.BountyGroup)
.Contains(p.Group));
return (completedCount - low) / (float)(high - low);
}
return 1f;
}
/// <summary>
/// Checks if the current station is the max rank
/// </summary>
public bool IsMaxRank(Entity<SalvageJobsDataComponent> ent)
{
return GetAvailableJobs(ent).Count == 0;
}
/// <summary>
/// Gets the current rank of the station
/// </summary>
public SalvageRankDatum GetRank(Entity<SalvageJobsDataComponent> ent)
{
if (IsMaxRank(ent))
return ent.Comp.MaxRank;
var completedCount = ent.Comp.CompletedJobs.Count;
foreach (var (threshold, rank) in ent.Comp.RankThresholds.Reverse())
{
if (completedCount < threshold)
continue;
return rank;
}
// base case
return ent.Comp.RankThresholds[0];
}
/// <summary>
///
/// </summary>
/// <param name="ent"></param>
/// <param name="job"></param>
/// <returns></returns>
public bool TryCompleteSalvageJob(Entity<SalvageJobsDataComponent> ent, ProtoId<CargoBountyPrototype> job)
{
if (!GetAvailableJobs(ent).Contains(job))
return false;
var jobProto = _prototypeManager.Index(job);
var oldRank = GetRank(ent);
ent.Comp.CompletedJobs.Add(job);
var newRank = GetRank(ent);
// Add reward
if (TryComp<StationBankAccountComponent>(ent, out var stationBankAccount))
{
_cargo.UpdateBankAccount(
(ent.Owner, stationBankAccount),
jobProto.Reward,
_cargo.CreateAccountDistribution((ent, stationBankAccount)));
}
// We ranked up!
if (oldRank != newRank)
{
// We need to find a computer to send the message from.
var computerQuery = EntityQueryEnumerator<SalvageJobBoardConsoleComponent>();
while (computerQuery.MoveNext(out var uid, out _))
{
var message = Loc.GetString("job-board-radio-announce", ("rank", FormattedMessage.RemoveMarkupPermissive(Loc.GetString(newRank.Title))));
_radio.SendRadioMessage(uid, message, UnlockChannel, uid, false);
break;
}
if (newRank.UnlockedMarket is { } market &&
TryComp<StationCargoOrderDatabaseComponent>(ent, out var stationCargoOrder))
{
stationCargoOrder.Markets.Add(market);
}
}
var enumerator = EntityQueryEnumerator<SalvageJobBoardConsoleComponent>();
while (enumerator.MoveNext(out var consoleUid, out var console))
{
UpdateUi((consoleUid, console), ent);
}
return true;
}
/// <summary>
/// Checks if a given entity fulfills a bounty for the station.
/// </summary>
public bool FulfillsSalvageJob(EntityUid uid, Entity<SalvageJobsDataComponent>? station, [NotNullWhen(true)] out ProtoId<CargoBountyPrototype>? job)
{
job = null;
if (!_label.TryGetLabel<JobBoardLabelComponent>(uid, out var labelEnt))
return false;
if (labelEnt.Value.Comp.JobId is not { } jobId)
return false;
job = jobId;
if (station is null)
{
if (_station.GetOwningStation(uid) is not { } stationUid ||
!TryComp<SalvageJobsDataComponent>(stationUid, out var stationComp))
return false;
station = (stationUid, stationComp);
}
if (!GetAvailableJobs((station.Value, station.Value.Comp)).Contains(job.Value))
return false;
if (!_cargo.IsBountyComplete(uid, job))
return false;
return true;
}
private void OnBUIOpened(Entity<SalvageJobBoardConsoleComponent> ent, ref BoundUIOpenedEvent args)
{
if (args.UiKey is not SalvageJobBoardUiKey.Key)
return;
if (_station.GetOwningStation(ent.Owner) is not { } station ||
!TryComp<SalvageJobsDataComponent>(station, out var jobData))
return;
UpdateUi(ent, (station, jobData));
}
private void OnPrintLabelMessage(Entity<SalvageJobBoardConsoleComponent> ent, ref JobBoardPrintLabelMessage args)
{
if (_timing.CurTime < ent.Comp.NextPrintTime)
return;
if (_station.GetOwningStation(ent) is not { } station ||
!TryComp<SalvageJobsDataComponent>(station, out var jobsData))
return;
if (!_prototypeManager.TryIndex<CargoBountyPrototype>(args.JobId, out var job))
return;
if (!GetAvailableJobs((station, jobsData)).Contains(args.JobId))
return;
_audio.PlayPvs(ent.Comp.PrintSound, ent);
var label = SpawnAtPosition(ent.Comp.LabelEntity, Transform(ent).Coordinates);
EnsureComp<JobBoardLabelComponent>(label).JobId = job.ID;
var target = new List<string>();
foreach (var entry in job.Entries)
{
target.Add(Loc.GetString("bounty-console-manifest-entry",
("amount", entry.Amount),
("item", Loc.GetString(entry.Name))));
}
_paper.SetContent(label, Loc.GetString("job-board-label-text", ("target", string.Join(',', target)), ("reward", job.Reward)));
ent.Comp.NextPrintTime = _timing.CurTime + ent.Comp.PrintDelay;
}
private void UpdateUi(Entity<SalvageJobBoardConsoleComponent> ent, Entity<SalvageJobsDataComponent> stationEnt)
{
var state = new SalvageJobBoardConsoleState(
GetRank(stationEnt).Title,
GetRankProgression(stationEnt),
GetAvailableJobs(stationEnt));
_ui.SetUiState(ent.Owner, SalvageJobBoardUiKey.Key, state);
}
}

View File

@@ -0,0 +1,61 @@
using Content.Shared.Cargo.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Server.Salvage.JobBoard;
/// <summary>
/// holds information for a station relating to the salvage job board
/// </summary>
[RegisterComponent]
[Access(typeof(SalvageJobBoardSystem))]
public sealed partial class SalvageJobsDataComponent : Component
{
/// <summary>
/// A dictionary relating the number of completed jobs needed to the different ranks.
/// </summary>
[DataField]
public SortedDictionary<int, SalvageRankDatum> RankThresholds = new();
/// <summary>
/// The rank given when all salvage jobs are complete.
/// </summary>
[DataField]
public SalvageRankDatum MaxRank;
/// <summary>
/// A list of all completed jobs in order.
/// </summary>
[DataField]
public List<ProtoId<CargoBountyPrototype>> CompletedJobs = new();
/// <summary>
/// Account where rewards are deposited.
/// </summary>
[DataField]
public ProtoId<CargoAccountPrototype> RewardAccount = "Cargo";
}
/// <summary>
/// Holds information about salvage job ranks
/// </summary>
[DataDefinition]
public partial record struct SalvageRankDatum
{
/// <summary>
/// The title displayed when this rank is reached
/// </summary>
[DataField]
public LocId Title;
/// <summary>
/// The bounties associated with this rank.
/// </summary>
[DataField]
public ProtoId<CargoBountyGroupPrototype>? BountyGroup;
/// <summary>
/// The market that is unlocked when you reach this rank
/// </summary>
[DataField]
public ProtoId<CargoMarketPrototype>? UnlockedMarket;
}

View File

@@ -1,3 +1,5 @@
using Content.Shared.Cargo.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Cargo.BUI;
@@ -10,13 +12,15 @@ public sealed class CargoConsoleInterfaceState : BoundUserInterfaceState
public int Capacity;
public NetEntity Station;
public List<CargoOrderData> Orders;
public List<ProtoId<CargoProductPrototype>> Products;
public CargoConsoleInterfaceState(string name, int count, int capacity, NetEntity station, List<CargoOrderData> orders)
public CargoConsoleInterfaceState(string name, int count, int capacity, NetEntity station, List<CargoOrderData> orders, List<ProtoId<CargoProductPrototype>> products)
{
Name = name;
Count = count;
Capacity = capacity;
Station = station;
Orders = orders;
Products = products;
}
}

View File

@@ -78,7 +78,13 @@ public sealed partial class CargoOrderConsoleComponent : Component
/// All of the <see cref="CargoProductPrototype.Group"/>s that are supported.
/// </summary>
[DataField, AutoNetworkedField]
public List<ProtoId<CargoMarketPrototype>> AllowedGroups = new() { "market" };
public List<ProtoId<CargoMarketPrototype>> AllowedGroups = new()
{
"market",
"SalvageJobReward2",
"SalvageJobReward3",
"SalvageJobRewardMAX",
};
/// <summary>
/// Access needed to toggle the limit on this console.

View File

@@ -0,0 +1,14 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Cargo.Prototypes;
/// <summary>
/// Used to categorize bounties for different purposes
/// </summary>
[Prototype]
public sealed partial class CargoBountyGroupPrototype : IPrototype
{
/// <inheritdoc/>
[IdDataField]
public string ID { get; private set; } = default!;
}

View File

@@ -1,6 +1,7 @@
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Cargo.Prototypes;
@@ -39,6 +40,18 @@ public sealed partial class CargoBountyPrototype : IPrototype
/// </summary>
[DataField]
public string IdPrefix = "NT";
/// <summary>
/// A group used for categorizing this bounty.
/// </summary>
[DataField]
public ProtoId<CargoBountyGroupPrototype> Group = "StationBounty";
/// <summary>
/// Optional sprite representing this bounty.
/// </summary>
[DataField]
public SpriteSpecifier? Sprite;
}
[DataDefinition, Serializable, NetSerializable]

View File

@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Examine;
using Content.Shared.Labels.Components;
@@ -141,4 +142,23 @@ public sealed partial class LabelSystem : EntitySystem
if (TryComp<PaperLabelTypeComponent>(slot.Item, out var type))
_appearance.SetData(ent, PaperLabelVisuals.LabelType, type.PaperType, ent.Comp2);
}
/// <summary>
/// Retrieves a label with the specified component from the default label slot.
/// </summary>
public bool TryGetLabel<T>(Entity<PaperLabelComponent?> ent, [NotNullWhen(true)] out Entity<T>? label) where T : Component
{
label = null;
if (!Resolve(ent, ref ent.Comp, false))
return false;
if (ent.Comp.LabelSlot.Item is not { } labelEnt)
return false;
if (!TryComp<T>(labelEnt, out var labelComp))
return false;
label = (labelEnt, labelComp);
return true;
}
}

View File

@@ -281,6 +281,12 @@ public sealed class PaperSystem : EntitySystem
}
}
public void SetContent(EntityUid entity, string content)
{
if (!TryComp<PaperComponent>(entity, out var paper))
return;
SetContent((entity, paper), content);
}
public void SetContent(Entity<PaperComponent> entity, string content)
{

View File

@@ -0,0 +1,72 @@
using Content.Shared.Cargo.Prototypes;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Salvage.JobBoard;
/// <summary>
/// Used to view the job board ui
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class SalvageJobBoardConsoleComponent : Component
{
/// <summary>
/// A label that this computer can print out.
/// </summary>
[DataField]
public EntProtoId LabelEntity = "PaperSalvageJobLabel";
/// <summary>
/// The sound made when printing occurs
/// </summary>
[DataField]
public SoundSpecifier PrintSound = new SoundPathSpecifier("/Audio/Machines/printer.ogg");
/// <summary>
/// The time at which the console will be able to print a label again.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan NextPrintTime = TimeSpan.Zero;
/// <summary>
/// The time between prints.
/// </summary>
[DataField]
public TimeSpan PrintDelay = TimeSpan.FromSeconds(5);
}
[Serializable, NetSerializable]
public sealed class SalvageJobBoardConsoleState : BoundUserInterfaceState
{
public string Title;
public float Progression;
public List<ProtoId<CargoBountyPrototype>> AvailableJobs;
public SalvageJobBoardConsoleState(string title, float progression, List<ProtoId<CargoBountyPrototype>> availableJobs)
{
Title = title;
Progression = progression;
AvailableJobs = availableJobs;
}
}
[Serializable, NetSerializable]
public sealed class JobBoardPrintLabelMessage : BoundUserInterfaceMessage
{
public string JobId;
public JobBoardPrintLabelMessage(string jobId)
{
JobId = jobId;
}
}
[Serializable, NetSerializable]
public enum SalvageJobBoardUiKey : byte
{
Key
}

View File

@@ -11,6 +11,7 @@ bounty-item-clown-shoes = Clown shoes
bounty-item-corn = Ear of corn
bounty-item-crayon = Crayon
bounty-item-cuban-carp = Cuban carp
bounty-item-diamond = Diamond
bounty-item-donk-pocket = Donk-pocket
bounty-item-donut = Donut
bounty-item-figurine = Action figure
@@ -25,6 +26,11 @@ bounty-item-lime = Lime
bounty-item-lung = Lung
bounty-item-monkey-cube = Monkey cube
bounty-item-mouse = Dead mouse
bounty-item-ore-bananium = Bananium ore
bounty-item-ore-gold = Gold ore
bounty-item-ore-plasma = Plasma ore
bounty-item-ore-silver = Silver ore
bounty-item-ore-uranium = Uranium ore
bounty-item-pancake = Pancake
bounty-item-pen = Pen
bounty-item-percussion = Percussion instrument
@@ -32,6 +38,7 @@ bounty-item-pie = Pie
bounty-item-prison-uniform = Prison uniform
bounty-item-radio = Radio or Headset
bounty-item-research-disk = Research disk
bounty-item-scrap = Scrap
bounty-item-shiv = Shiv
bounty-item-soap = Soap
bounty-item-soup = Soup
@@ -139,8 +146,4 @@ bounty-description-wine = The new librarian and the Quartermaster are falling he
bounty-description-cotton-boll = A massive swarm of mothroaches ate all the paper and cloth on the station. Send us some cotton to help keep our winged crewmembers fed.
bounty-description-microwave-machine-board = Mr. Giggles thought it'd be funny to stick forks in all the kitchen microwaves. Help us replace them before the chefs start making clown burgers.
bounty-description-flashes = GREETINGS \[Station] WE REQUIRE 6 FLASHES DUE TO A NORMAL \[TrainingExercise] WITH SECURITY. EVERYTHING IS \[Normal].
bounty-description-tooth-space-carp = Some lads from "down unda" need some teeth to make their traditional apparel. Send them a few from some space carp.
bounty-description-tooth-sharkminnow = The chef is claiming that the teeth of sharkminnows are some kind of high-quality knife. I don't know what they're on about, but they want a set. Send it to them.
bounty-description-ring = On this EXTRAORDINARY day there will be a wedding between the Gelts, but Mr. Gelt has lost the rings, send a pair of rings.
bounty-description-remains = TWO. HIVELORD REMAINS, please.
bounty-description-plates = Our club is interested in trophies of majestic creatures such as the Goliath: send a couple of plates. The reward will come quickly.

View File

@@ -2,3 +2,4 @@
price-gun-verb-text = Appraisal
price-gun-verb-message = Appraise {THE($object)}.
price-gun-bounty-complete = The device confirms that the bounty contained within is completed.
price-gun-salvjob-complete = The device confirms that the salvage job contained within is completed.

View File

@@ -94,3 +94,5 @@ command-description-xenoartifact-averageResearch =
Calculates amount of research points average generated xeno artifact will output when fully activated.
command-description-xenoartifact-unlockAllNodes =
Unlocks all nodes of artifact.
command-description-jobboard-completeJob =
Completes a given salvage job board job for the station.

View File

@@ -0,0 +1,47 @@
salvage-job-rank-title-0 = [color=gray]Scavenger[/color]
salvage-job-rank-title-1 = [color=white]Scrapper[/color]
salvage-job-rank-title-2 = [color=yellow]Specialist[/color]
salvage-job-rank-title-MAX = [color=gold]Supreme Salvager[/color]
job-board-radio-announce = Salvager rank increased to [bold]{$rank}[/bold]! New orders can be purchased from Cargo.
job-board-ui-window-title = Job Board
job-board-ui-label-rank = [bold]Rank:[/bold]
job-board-ui-label-items = Target: [color=red]{$item}[/color]
job-board-label-text = [head=2]Salvage Job Shipment[/head]
{"[italic]For use only on official off-station salvage shipments.[/italic]"}
{"[bold]Target:[/bold]"} {$target}
{"[bold]Reward:[/bold]"} ${$reward}
{"[italic]Shipments are subject to inspection by the Donk corporation[/italic]"}
salv-job-board-name-BountyTeethSpaceCarp = Space Carp
salv-job-board-name-BountySalvageScrap = Deep-Space Debris
salv-job-board-name-BountySalvageOreGold = Gold (Ore)
salv-job-board-name-BountySalvageOreSilver = Silver (Ore)
salv-job-board-name-BountySalvageOreUranium = Uranium (Ore)
salv-job-board-name-BountySalvageOrePlasma = Plasma (Ore)
salv-job-board-name-BountySalvageOreBananium = Bananium (Ore)
salv-job-board-name-BountyTeethSharkminnow = Sharkminnow
salv-job-board-name-BountyGoliathPlates = Goliath
salv-job-board-name-BountyHivelordRemains = Hivelord
salv-job-board-name-BountySalvageDiamond = Diamond
bounty-description-tooth-space-carp = We need you to get a sample of some space carp teeth. You can find these guys on all kinds of salvage debris. Just be careful about their bite.
bounty-description-salvage-scrap = We are researching the effects of deep space on station materials, and we need some samples. Find some old junk off of debris and bring it to us.
bounty-description-salvage-ore-gold = We are engaging in an experimental new electronics manufacturing process. Deliver us a large sum of unrefined gold ore. It can come from any source.
bounty-description-salvage-ore-silver = We are studying the material effects of silver based on the refining methods. Send us a large amount of unrefined silver ore. It can come from any source.
bounty-description-tooth-sharkminnow = We need you to get a sample of some Sharkminnow teeth. These guys are a fair bit nastier than the smaller carp you're familiar with. Take care to not let them bite you: they'll suck out your blood and heal.
bounty-description-salvage-ore-plasma = We need a shipment of plasma ore to send over to the research station. Please provide us with some so that we can continue our testing. It can come from any source.
bounty-description-salvage-ore-uranium = We need a sample of uranium ore for our ongoing experiments on nuclear devices. Be aware that while the uranium does glow slightly, it will probably not harm you. It can come from any source.
bounty-description-salvage-ore-bananium = We have an ongoing project to decode the mystifying clown genomic sequence. We believe a sample of raw bananium will help us achieve this. Note that this only comes from the rarest of deep-space asteroids.
bounty-description-remains = We need you to get a sample of a few Hivelord cores. Be aware that Hivelords can replicate infinitely if the core is not destroyed. Take care not to get overwhelmed.
bounty-description-plates = We need you to get a couple sheets of Goliath hide. These guys are pretty slow, but be careful about the tentacles: they'll grab you and pull you to the ground. You don't want to know what happens next.
bounty-description-diamond = We need you to acquire a few diamonds for some advanced fabrication. These can either be found in the mining asteroid nearby or cut out of the basilisk creature. Whichever way you want to do it, get us some.

View File

@@ -749,50 +749,6 @@
components:
- Flash
- type: cargoBounty
id: BountyTeethSpaceCarp
reward: 7500
description: bounty-description-tooth-space-carp
entries:
- name: bounty-item-tooth-space-carp
amount: 8
whitelist:
tags:
- ToothSpaceCarp
- type: cargoBounty
id: BountyTeethSharkminnow
reward: 15000
description: bounty-description-tooth-sharkminnow
entries:
- name: bounty-item-tooth-sharkminnow
amount: 5
whitelist:
tags:
- ToothSharkminnow
- type: cargoBounty
id: BountyPlates
reward: 20000
description: bounty-description-plates
entries:
- name: bounty-item-plates
amount: 4
whitelist:
tags:
- GoliathPlate
- type: cargoBounty
id: BountyRemains
reward: 15000
description: bounty-description-remains
entries:
- name: bounty-item-remains
amount: 2
whitelist:
tags:
- HivelordRemains
- type: cargoBounty
id: BountyRing
reward: 12500

View File

@@ -0,0 +1,11 @@
- type: cargoBountyGroup
id: StationBounty
- type: cargoBountyGroup
id: SalvageJobTier1
- type: cargoBountyGroup
id: SalvageJobTier2
- type: cargoBountyGroup
id: SalvageJobTier3

View File

@@ -0,0 +1,173 @@
# NOTE: if you add any bounties to this, you need to go to Resources/Prototypes/Entities/Stations/base.yml and adjust the thresholds on BaseStationSalvageJobs.
# If you don't do this, you won't raise the limit for completing a given rank and may throw off some balance.
# Tier 1
- type: cargoBounty
id: BountyTeethSpaceCarp
reward: 7500
description: bounty-description-tooth-space-carp
group: SalvageJobTier1
sprite:
sprite: Mobs/Aliens/Carps/space.rsi
state: icon
entries:
- name: bounty-item-tooth-space-carp
amount: 10
whitelist:
tags:
- ToothSpaceCarp
- type: cargoBounty
id: BountySalvageScrap
reward: 7500
description: bounty-description-salvage-scrap
group: SalvageJobTier1
sprite:
sprite: Objects/Materials/Scrap/generic.rsi
state: metal-1
entries:
- name: bounty-item-scrap
amount: 15
whitelist:
tags:
- SalvageScrap
- type: cargoBounty
id: BountySalvageOreGold
reward: 7500
description: bounty-description-salvage-ore-gold
group: SalvageJobTier1
sprite:
sprite: Objects/Materials/ore.rsi
state: gold
entries:
- name: bounty-item-ore-gold
amount: 90
whitelist:
tags:
- OreGold
- type: cargoBounty
id: BountySalvageOreSilver
reward: 7500
description: bounty-description-salvage-ore-silver
group: SalvageJobTier1
sprite:
sprite: Objects/Materials/ore.rsi
state: silver
entries:
- name: bounty-item-ore-silver
amount: 90
whitelist:
tags:
- OreSilver
# Tier 2
- type: cargoBounty
id: BountyTeethSharkminnow
reward: 12500
description: bounty-description-tooth-sharkminnow
group: SalvageJobTier2
sprite:
sprite: Mobs/Aliens/Carps/sharkminnow.rsi
state: icon
entries:
- name: bounty-item-tooth-sharkminnow
amount: 3
whitelist:
tags:
- ToothSharkminnow
- type: cargoBounty
id: BountySalvageOrePlasma
reward: 12500
description: bounty-description-salvage-ore-plasma
group: SalvageJobTier2
sprite:
sprite: Objects/Materials/ore.rsi
state: plasma
entries:
- name: bounty-item-ore-plasma
amount: 45
whitelist:
tags:
- OrePlasma
- type: cargoBounty
id: BountySalvageOreUranium
reward: 12500
description: bounty-description-salvage-ore-uranium
group: SalvageJobTier2
sprite:
sprite: Objects/Materials/ore.rsi
state: uranium
entries:
- name: bounty-item-ore-uranium
amount: 45
whitelist:
tags:
- OreUranium
- type: cargoBounty
id: BountySalvageOreBananium
reward: 12500
description: bounty-description-salvage-ore-bananium
group: SalvageJobTier2
sprite:
sprite: Objects/Materials/ore.rsi
state: bananium
entries:
- name: bounty-item-ore-bananium
amount: 30
whitelist:
tags:
- OreBananium
# Tier 3
- type: cargoBounty
id: BountyGoliathPlates
reward: 20000
description: bounty-description-plates
group: SalvageJobTier3
sprite:
sprite: Mobs/Aliens/Asteroid/goliath.rsi
state: goliath
entries:
- name: bounty-item-plates
amount: 6
whitelist:
tags:
- GoliathPlate
- type: cargoBounty
id: BountyHivelordRemains
reward: 20000
description: bounty-description-remains
group: SalvageJobTier3
sprite:
sprite: Mobs/Aliens/Asteroid/hivelord.rsi
state: hivelord
entries:
- name: bounty-item-remains
amount: 3
whitelist:
tags:
- HivelordRemains
- type: cargoBounty
id: BountySalvageDiamond
reward: 20000
description: bounty-description-diamond
group: SalvageJobTier3
sprite:
sprite: Objects/Materials/materials.rsi
state: diamond
entries:
- name: bounty-item-diamond
amount: 3
whitelist:
tags:
- Diamond

View File

@@ -1,2 +1,11 @@
- type: cargoMarket
id: market
- type: cargoMarket
id: SalvageJobReward2
- type: cargoMarket
id: SalvageJobReward3
- type: cargoMarket
id: SalvageJobRewardMAX

View File

@@ -9,6 +9,7 @@
- id: CargoSaleComputerCircuitboard
- id: CargoShuttleConsoleCircuitboard
- id: SalvageMagnetMachineCircuitboard
- id: SalvageJobBoardComputerCircuitboard
- id: CigPackGreen
prob: 0.50
- id: ClothingHeadsetAltCargo

View File

@@ -243,8 +243,7 @@
- id: FoodMeatFish
amount: 4
- id: MaterialToothSharkminnow1
amount: 1
maxAmount: 3
amount: 3
- type: MeleeWeapon
damage:
types:

View File

@@ -198,6 +198,17 @@
prototype: ComputerCargoBounty
- type: StaticPrice
- type: entity
parent: BaseComputerCircuitboard
id: SalvageJobBoardComputerCircuitboard
name: salvage job board computer board
description: A computer printed circuit board for a salvage job board computer.
components:
- type: Sprite
state: cpu_supply
- type: ComputerBoard
prototype: ComputerSalvageJobBoard
- type: entity
parent: BaseComputerCircuitboard
id: SalvageExpeditionsComputerCircuitboard

View File

@@ -389,6 +389,10 @@
- type: PhysicalComposition
materialComposition:
Diamond: 100
- type: Tag
tags:
- RawMaterial
- Diamond
- type: entity
parent: MaterialDiamond

View File

@@ -48,6 +48,10 @@
Quantity: 10
- type: Item
heldPrefix: gold
- type: Tag
tags:
- Ore
- OreGold
- type: entity
parent: GoldOre
@@ -152,6 +156,10 @@
Quantity: 10
- type: Item
heldPrefix: plasma
- type: Tag
tags:
- Ore
- OrePlasma
- type: entity
parent: PlasmaOre
@@ -185,6 +193,10 @@
Quantity: 10
- type: Item
heldPrefix: silver
- type: Tag
tags:
- Ore
- OreSilver
- type: entity
parent: SilverOre
@@ -259,6 +271,10 @@
canReact: false
- type: Item
heldPrefix: uranium
- type: Tag
tags:
- Ore
- OreUranium
- type: entity
parent: UraniumOre
@@ -301,6 +317,10 @@
Quantity: 5
- type: Item
heldPrefix: bananium
- type: Tag
tags:
- Ore
- OreBananium
- type: entity
parent: BananiumOre

View File

@@ -25,6 +25,7 @@
- type: Tag
tags:
- Recyclable
- SalvageScrap
- type: entity
parent: BaseStructure

View File

@@ -253,6 +253,41 @@
- CargoBounties
- Cargo
- type: entity
id: PaperSalvageJobLabel
parent: PaperCargoInvoice
name: salvage job shipment label
description: A paper label designating a crate as containing a shipment to fulfill a salvage job. Selling a crate with this will fulfill the job.
components:
- type: Sprite
layers:
- state: paper
color: "#f7e574"
- state: paper_words
map: ["enum.PaperVisualLayers.Writing"]
color: "#f7e574"
visible: false
- state: paper_stamp-generic
map: ["enum.PaperVisualLayers.Stamp"]
visible: false
- type: PaperLabelType
paperType: Bounty
- type: Tag
tags:
- Document
- Trash
- Paper
- type: PaperVisuals
backgroundImagePath: "/Textures/Interface/Paper/paper_background_default.svg.96dpi.png"
contentImagePath: "/Textures/Interface/Paper/paper_content_lined.svg.96dpi.png"
backgroundModulate: "#f7e574"
contentImageModulate: "#f7e574"
backgroundPatchMargin: 16.0, 16.0, 16.0, 16.0
contentMargin: 16.0, 16.0, 16.0, 16.0
- type: JobBoardLabel
- type: StaticPrice #infinitely printable
price: 0
- type: entity
name: character sheet
parent: Paper

View File

@@ -144,6 +144,27 @@
components:
- type: SalvageMagnetData
- type: entity
id: BaseStationSalvageJobs
abstract: true
components:
- type: SalvageJobsData
rankThresholds:
0:
title: salvage-job-rank-title-0
bountyGroup: SalvageJobTier1
3:
title: salvage-job-rank-title-1
bountyGroup: SalvageJobTier2
unlockedMarket: SalvageJobReward2
6:
title: salvage-job-rank-title-2
bountyGroup: SalvageJobTier3
unlockedMarket: SalvageJobReward3
maxRank:
title: salvage-job-rank-title-MAX
unlockedMarket: SalvageJobRewardMAX
- type: entity
id: BaseStationSiliconLawCrewsimov
abstract: true

View File

@@ -22,6 +22,7 @@
- BaseStationAlertLevels
- BaseStationMagnet
- BaseStationExpeditions
- BaseStationSalvageJobs
- BaseStationSiliconLawCrewsimov
- BaseStationAllEventsEligible
- BaseStationNanotrasen

View File

@@ -1202,6 +1202,42 @@
guides:
- Cloning
- type: entity
parent: BaseComputerAiAccess
id: ComputerSalvageJobBoard
name: salvage job board
description: Console for accessing salvage jobs, if you're tough enough.
components:
- type: Sprite
layers:
- map: ["computerLayerBody"]
state: computer
- map: ["computerLayerKeyboard"]
state: generic_keyboard
- map: ["computerLayerScreen"]
state: salvjob # givin em a salvjob like hawk tuah
- map: ["computerLayerKeys"]
state: generic_keys
- map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
state: generic_panel_open
- type: SalvageJobBoardConsole
- type: ActivatableUI
key: enum.SalvageJobBoardUiKey.Key
- type: UserInterface
interfaces:
enum.SalvageJobBoardUiKey.Key:
type: SalvageJobBoardBoundUserInterface
enum.WiresUiKey.Key:
type: WiresBoundUserInterface
- type: ActiveRadio
channels: [ Supply ]
- type: Computer
board: SalvageJobBoardComputerCircuitboard
- type: PointLight
radius: 1.5
energy: 1.6
color: "#b89f25"
- type: entity
id: ComputerSalvageExpedition
parent: BaseComputerAiAccess

View File

@@ -443,6 +443,9 @@
- type: Tag
id: Diagonal
- type: Tag
id: Diamond
- type: Tag
id: Dice
@@ -985,6 +988,21 @@
- type: Tag
id: Ore
- type: Tag
id: OreBananium
- type: Tag
id: OreGold
- type: Tag
id: OrePlasma
- type: Tag
id: OreSilver
- type: Tag
id: OreUranium
- type: Tag
id: Packet
@@ -1163,6 +1181,9 @@
- type: Tag
id: SalvageExperiment
- type: Tag
id: SalvageScrap
- type: Tag
id: Scarf

View File

@@ -1672,6 +1672,44 @@
"name": "robot",
"directions": 4
},
{
"name": "salvjob",
"directions": 4,
"delays": [
[
1,
0.1,
1,
0.1,
1,
0.1
],
[
1,
0.1,
1,
0.1,
1,
0.1
],
[
1,
0.1,
1,
0.1,
1,
0.1
],
[
1,
0.1,
1,
0.1,
1,
0.1
]
]
},
{
"name": "security",
"directions": 4,

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB