using Content.Shared.Cargo;
using Content.Client.Cargo.UI;
using Content.Shared.Cargo.BUI;
+using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Events;
using Content.Shared.Cargo.Prototypes;
using Content.Shared.IdentityManagement;
{
public sealed class CargoOrderConsoleBoundUserInterface : BoundUserInterface
{
+ private readonly SharedCargoSystem _cargoSystem;
+
[ViewVariables]
private CargoConsoleMenu? _menu;
public CargoOrderConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
+ _cargoSystem = EntMan.System<SharedCargoSystem>();
}
protected override void Open()
string orderRequester;
- if (EntMan.TryGetComponent<MetaDataComponent>(localPlayer, out var metadata))
+ if (EntMan.EntityExists(localPlayer))
orderRequester = Identity.Name(localPlayer.Value, EntMan);
else
orderRequester = string.Empty;
}
};
+ _menu.OnAccountAction += (account, amount) =>
+ {
+ SendMessage(new CargoConsoleWithdrawFundsMessage(account, amount));
+ };
+
+ _menu.OnToggleUnboundedLimit += _ =>
+ {
+ SendMessage(new CargoConsoleToggleLimitMessage());
+ };
+
_menu.OpenCentered();
}
private void Populate(List<CargoOrderData> orders)
{
- if (_menu == null) return;
+ if (_menu == null)
+ return;
_menu.PopulateProducts();
_menu.PopulateCategories();
_menu.PopulateOrders(orders);
+ _menu.PopulateAccountActions();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
- if (state is not CargoConsoleInterfaceState cState)
+ if (state is not CargoConsoleInterfaceState cState || !EntMan.TryGetComponent<CargoOrderConsoleComponent>(Owner, out var orderConsole))
return;
+ var station = EntMan.GetEntity(cState.Station);
OrderCapacity = cState.Capacity;
OrderCount = cState.Count;
- BankBalance = cState.Balance;
+ BankBalance = _cargoSystem.GetBalanceFromAccount(station, orderConsole.Account);
AccountName = cState.Name;
+ _menu?.UpdateStation(station);
Populate(cState.Orders);
- _menu?.UpdateCargoCapacity(OrderCount, OrderCapacity);
- _menu?.UpdateBankData(AccountName, BankBalance);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
- if (!disposing) return;
+ if (!disposing)
+ return;
_menu?.Dispose();
_orderMenu?.Dispose();
return;
SendMessage(new CargoConsoleApproveOrderMessage(row.Order.OrderId));
- // Most of the UI isn't predicted anyway so.
- // _menu?.UpdateCargoCapacity(OrderCount + row.Order.Amount, OrderCapacity);
}
}
}
--- /dev/null
+using Content.Client.Cargo.UI;
+using Content.Shared.Cargo.Components;
+using JetBrains.Annotations;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Cargo.BUI;
+
+[UsedImplicitly]
+public sealed class FundingAllocationConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
+{
+ [ViewVariables]
+ private FundingAllocationMenu? _menu;
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _menu = this.CreateWindow<FundingAllocationMenu>();
+
+ _menu.OnSavePressed += d =>
+ {
+ SendMessage(new SetFundingAllocationBuiMessage(d));
+ };
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState message)
+ {
+ base.UpdateState(message);
+
+ if (message is not FundingAllocationConsoleBuiState state)
+ return;
+
+ _menu?.Update(state);
+ }
+}
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
SetSize="600 600"
MinSize="600 600">
- <BoxContainer Orientation="Vertical" Margin="5 0 5 0">
+ <BoxContainer Orientation="Vertical" Margin="15 5 15 10">
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'cargo-console-menu-account-name-label'}"
StyleClasses="LabelKeyText" />
- <Label Name="AccountNameLabel"
+ <RichTextLabel Name="AccountNameLabel"
Text="{Loc 'cargo-console-menu-account-name-none-text'}" />
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'cargo-console-menu-points-label'}"
StyleClasses="LabelKeyText" />
- <Label Name="PointsLabel"
+ <RichTextLabel Name="PointsLabel"
Text="$0" />
</BoxContainer>
- <BoxContainer Orientation="Horizontal">
- <Label Text="{Loc 'cargo-console-menu-order-capacity-label'}"
- StyleClasses="LabelKeyText" />
- <Label Name="ShuttleCapacityLabel"
- Text="0/20" />
- </BoxContainer>
- <BoxContainer Orientation="Horizontal">
- <OptionButton Name="Categories"
- Prefix="{Loc 'cargo-console-menu-categories-label'}"
- HorizontalExpand="True" />
- <LineEdit Name="SearchBar"
- PlaceHolder="{Loc 'cargo-console-menu-search-bar-placeholder'}"
- HorizontalExpand="True" />
- </BoxContainer>
- <ScrollContainer HorizontalExpand="True"
- VerticalExpand="True"
- SizeFlagsStretchRatio="6">
- <BoxContainer Name="Products"
- Orientation="Vertical"
- HorizontalExpand="True"
- VerticalExpand="True">
- <!-- Products get added here by code -->
- </BoxContainer>
- </ScrollContainer>
- <PanelContainer VerticalExpand="True"
- SizeFlagsStretchRatio="6">
- <PanelContainer.PanelOverride>
- <gfx:StyleBoxFlat BackgroundColor="#000000" />
- </PanelContainer.PanelOverride>
- <ScrollContainer VerticalExpand="True">
- <BoxContainer Orientation="Vertical">
- <Label Text="{Loc 'cargo-console-menu-requests-label'}" />
- <BoxContainer Name="Requests"
- Orientation="Vertical"
- VerticalExpand="True">
- <!-- Requests are added here by code -->
- </BoxContainer>
- <Label Text="{Loc 'cargo-console-menu-orders-label'}" />
- <BoxContainer Name="Orders"
+ <Control MinHeight="10"/>
+ <TabContainer Name="TabContainer" VerticalExpand="True">
+ <BoxContainer Orientation="Vertical" VerticalExpand="True">
+ <BoxContainer Orientation="Horizontal">
+ <OptionButton Name="Categories"
+ Prefix="{Loc 'cargo-console-menu-categories-label'}"
+ HorizontalExpand="True" />
+ <LineEdit Name="SearchBar"
+ PlaceHolder="{Loc 'cargo-console-menu-search-bar-placeholder'}"
+ HorizontalExpand="True" />
+ </BoxContainer>
+ <Control MinHeight="5"/>
+ <ScrollContainer HorizontalExpand="True"
+ VerticalExpand="True"
+ SizeFlagsStretchRatio="2">
+ <BoxContainer Name="Products"
Orientation="Vertical"
- StyleClasses="transparentItemList"
+ HorizontalExpand="True"
VerticalExpand="True">
- <!-- Orders are added here by code -->
+ <!-- Products get added here by code -->
</BoxContainer>
+ </ScrollContainer>
+ <Control MinHeight="5"/>
+ <PanelContainer VerticalExpand="True"
+ SizeFlagsStretchRatio="1">
+ <PanelContainer.PanelOverride>
+ <gfx:StyleBoxFlat BackgroundColor="#000000" />
+ </PanelContainer.PanelOverride>
+ <ScrollContainer VerticalExpand="True">
+ <BoxContainer Orientation="Vertical" Margin="5">
+ <Label Text="{Loc 'cargo-console-menu-requests-label'}" />
+ <BoxContainer Name="Requests"
+ Orientation="Vertical"
+ VerticalExpand="True">
+ <!-- Requests are added here by code -->
+ </BoxContainer>
+ </BoxContainer>
+ </ScrollContainer>
+ </PanelContainer>
+ </BoxContainer>
+ <!-- Funds tab -->
+ <BoxContainer Orientation="Vertical" Margin="15">
+ <BoxContainer Orientation="Horizontal">
+ <RichTextLabel Name="TransferLimitLabel" Margin="0 0 15 0"/>
+ <RichTextLabel Name="UnlimitedNotifier" Text="{Loc 'cargo-console-menu-account-action-transfer-limit-unlimited-notifier'}"/>
+ </BoxContainer>
+ <BoxContainer Orientation="Horizontal">
+ <RichTextLabel Text="{Loc 'cargo-console-menu-account-action-select'}" Margin="0 0 10 0"/>
+ <OptionButton Name="ActionOptions"/>
</BoxContainer>
- </ScrollContainer>
- </PanelContainer>
- <TextureButton VerticalExpand="True" />
+ <Control MinHeight="5"/>
+ <BoxContainer Orientation="Horizontal">
+ <RichTextLabel Name="AmountText" Text="{ Loc 'cargo-console-menu-account-action-amount'}"/>
+ <SpinBox Name="TransferSpinBox" MinWidth="100" Value="10"/>
+ </BoxContainer>
+ <Control MinHeight="15"/>
+ <BoxContainer HorizontalAlignment="Center">
+ <Button Name="AccountActionButton" Text="{ Loc 'cargo-console-menu-account-action-button'}" MinHeight="45" MinWidth="120"/>
+ </BoxContainer>
+ <Control VerticalExpand="True"/>
+ <BoxContainer VerticalAlignment="Bottom" HorizontalAlignment="Center">
+ <Button Name="AccountLimitToggleButton" Text="{ Loc 'cargo-console-menu-toggle-account-lock-button'}" MinHeight="45" MinWidth="120"/>
+ </BoxContainer>
+ </BoxContainer>
+ </TabContainer>
</BoxContainer>
</controls:FancyWindow>
using System.Linq;
+using Content.Client.Cargo.Systems;
using Content.Client.UserInterface.Controls;
using Content.Shared.Cargo;
using Content.Shared.Cargo.Components;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
using static Robust.Client.UserInterface.Controls.BaseButton;
namespace Content.Client.Cargo.UI
[GenerateTypedNameReferences]
public sealed partial class CargoConsoleMenu : FancyWindow
{
- private IEntityManager _entityManager;
- private IPrototypeManager _protoManager;
- private SpriteSystem _spriteSystem;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ private readonly IEntityManager _entityManager;
+ private readonly IPrototypeManager _protoManager;
+ private readonly CargoSystem _cargoSystem;
+ private readonly SpriteSystem _spriteSystem;
private EntityUid _owner;
+ private EntityUid? _station;
+
+ private readonly EntityQuery<CargoOrderConsoleComponent> _orderConsoleQuery;
+ private readonly EntityQuery<StationBankAccountComponent> _bankQuery;
public event Action<ButtonEventArgs>? OnItemSelected;
public event Action<ButtonEventArgs>? OnOrderApproved;
public event Action<ButtonEventArgs>? OnOrderCanceled;
+ public event Action<ProtoId<CargoAccountPrototype>?, int>? OnAccountAction;
+
+ public event Action<ButtonEventArgs>? OnToggleUnboundedLimit;
+
private readonly List<string> _categoryStrings = new();
private string? _category;
public CargoConsoleMenu(EntityUid owner, IEntityManager entMan, IPrototypeManager protoManager, SpriteSystem spriteSystem)
{
RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
_entityManager = entMan;
_protoManager = protoManager;
+ _cargoSystem = entMan.System<CargoSystem>();
_spriteSystem = spriteSystem;
_owner = owner;
- Title = Loc.GetString("cargo-console-menu-title");
+ _orderConsoleQuery = _entityManager.GetEntityQuery<CargoOrderConsoleComponent>();
+ _bankQuery = _entityManager.GetEntityQuery<StationBankAccountComponent>();
+
+ Title = entMan.GetComponent<MetaDataComponent>(owner).EntityName;
SearchBar.OnTextChanged += OnSearchBarTextChanged;
Categories.OnItemSelected += OnCategoryItemSelected;
+
+ if (entMan.TryGetComponent<CargoOrderConsoleComponent>(owner, out var orderConsole))
+ {
+ var accountProto = _protoManager.Index(orderConsole.Account);
+ AccountNameLabel.Text = Loc.GetString("cargo-console-menu-account-name-format",
+ ("color", accountProto.Color),
+ ("name", Loc.GetString(accountProto.Name)),
+ ("code", Loc.GetString(accountProto.Code)));
+ }
+
+ TabContainer.SetTabTitle(0, Loc.GetString("cargo-console-menu-tab-title-orders"));
+ TabContainer.SetTabTitle(1, Loc.GetString("cargo-console-menu-tab-title-funds"));
+
+ ActionOptions.OnItemSelected += idx =>
+ {
+ ActionOptions.SelectId(idx.Id);
+ };
+
+ TransferSpinBox.IsValid = val =>
+ {
+ if (!_entityManager.TryGetComponent<CargoOrderConsoleComponent>(owner, out var console) ||
+ !_entityManager.TryGetComponent<StationBankAccountComponent>(_station, out var bank))
+ return true;
+
+ return val >= 0 && val <= (int) (console.TransferLimit * bank.Accounts[console.Account]);
+ };
+
+ AccountActionButton.OnPressed += _ =>
+ {
+ var account = (ProtoId<CargoAccountPrototype>?) ActionOptions.SelectedMetadata;
+ OnAccountAction?.Invoke(account, TransferSpinBox.Value);
+ };
+
+ AccountLimitToggleButton.OnPressed += a =>
+ {
+ OnToggleUnboundedLimit?.Invoke(a);
+ };
}
private void OnCategoryItemSelected(OptionButton.ItemSelectedEventArgs args)
/// </summary>
public void PopulateOrders(IEnumerable<CargoOrderData> orders)
{
- Orders.DisposeAllChildren();
Requests.DisposeAllChildren();
foreach (var order in orders)
{
+ if (order.Approved)
+ continue;
+
var product = _protoManager.Index<EntityPrototype>(order.ProductId);
var productName = product.Name;
("orderAmount", order.OrderQuantity),
("orderRequester", order.Requester))
},
- Description = {Text = Loc.GetString("cargo-console-menu-order-reason-description",
- ("reason", order.Reason))}
+ Description =
+ {
+ Text = Loc.GetString("cargo-console-menu-order-reason-description",
+ ("reason", order.Reason))
+ }
};
row.Cancel.OnPressed += (args) => { OnOrderCanceled?.Invoke(args); };
- if (order.Approved)
- {
- row.Approve.Visible = false;
- row.Cancel.Visible = false;
- Orders.AddChild(row);
- }
- else
- {
- // TODO: Disable based on access.
- row.Approve.OnPressed += (args) => { OnOrderApproved?.Invoke(args); };
- Requests.AddChild(row);
- }
+
+ // TODO: Disable based on access.
+ row.Approve.OnPressed += (args) => { OnOrderApproved?.Invoke(args); };
+ Requests.AddChild(row);
}
}
- public void UpdateCargoCapacity(int count, int capacity)
+ public void PopulateAccountActions()
{
- // TODO: Rename + Loc.
- ShuttleCapacityLabel.Text = $"{count}/{capacity}";
+ if (!_entityManager.TryGetComponent<StationBankAccountComponent>(_station, out var bank) ||
+ !_entityManager.TryGetComponent<CargoOrderConsoleComponent>(_owner, out var console))
+ return;
+
+ var i = 0;
+ ActionOptions.Clear();
+ ActionOptions.AddItem(Loc.GetString("cargo-console-menu-account-action-option-withdraw"), i);
+ i++;
+ foreach (var account in bank.Accounts.Keys)
+ {
+ if (account == console.Account)
+ continue;
+ var accountProto = _protoManager.Index(account);
+ ActionOptions.AddItem(Loc.GetString("cargo-console-menu-account-action-option-transfer",
+ ("code", Loc.GetString(accountProto.Code))),
+ i);
+ ActionOptions.SetItemMetadata(i, account);
+ i++;
+ }
}
- public void UpdateBankData(string name, int points)
+ public void UpdateStation(EntityUid station)
{
- AccountNameLabel.Text = name;
- PointsLabel.Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", points.ToString()));
+ _station = station;
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ if (!_bankQuery.TryComp(_station, out var bankAccount) ||
+ !_orderConsoleQuery.TryComp(_owner, out var orderConsole))
+ {
+ return;
+ }
+
+ var balance = _cargoSystem.GetBalanceFromAccount((_station.Value, bankAccount), orderConsole.Account);
+ PointsLabel.Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", balance));
+ TransferLimitLabel.Text = Loc.GetString("cargo-console-menu-account-action-transfer-limit",
+ ("limit", (int) (balance * orderConsole.TransferLimit)));
+
+ UnlimitedNotifier.Visible = orderConsole.TransferUnbounded;
+ AccountActionButton.Disabled = TransferSpinBox.Value <= 0 ||
+ TransferSpinBox.Value > bankAccount.Accounts[orderConsole.Account] * orderConsole.TransferLimit ||
+ _timing.CurTime < orderConsole.NextAccountActionTime;
}
}
}
<PanelContainer xmlns="https://spacestation14.io"
- HorizontalExpand="True">
+ HorizontalExpand="True"
+ Margin="0 1">
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True">
<TextureRect Name="Icon"
Access="Public"
MinSize="32 32"
RectClipContent="True" />
+ <Control MinWidth="5"/>
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">
<Button Name="Approve"
Access="Public"
Text="{Loc 'cargo-console-menu-cargo-order-row-approve-button'}"
- StyleClasses="LabelSubText" />
+ StyleClasses="OpenRight" />
<Button Name="Cancel"
Access="Public"
Text="{Loc 'cargo-console-menu-cargo-order-row-cancel-button'}"
- StyleClasses="LabelSubText" />
+ StyleClasses="OpenLeft" />
</BoxContainer>
</PanelContainer>
ToolTip=""
Access="Public"
HorizontalExpand="True"
- VerticalExpand="True" />
+ VerticalExpand="True"
+ StyleClasses="OpenBoth"/>
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True">
<TextureRect Name="Icon"
<Label Name="PointCost"
Access="Public"
MinSize="52 32"
- Align="Right" />
+ Align="Right"
+ Margin="0 0 5 0"/>
</PanelContainer>
</BoxContainer>
</PanelContainer>
--- /dev/null
+<controls:FancyWindow xmlns="https://spacestation14.io"
+ xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+ xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
+ Title="{Loc 'cargo-funding-alloc-console-menu-title'}"
+ SetSize="680 310"
+ MinSize="680 310">
+ <BoxContainer Orientation="Vertical"
+ VerticalExpand="True"
+ HorizontalExpand="True"
+ Margin="10 5 10 10">
+ <Label Name="HelpLabel" HorizontalAlignment="Center" StyleClasses="LabelSubText"/>
+ <Control MinHeight="10"/>
+ <PanelContainer VerticalExpand="True" HorizontalExpand="True" VerticalAlignment="Top" MaxHeight="250">
+ <PanelContainer.PanelOverride>
+ <graphics:StyleBoxFlat BackgroundColor="#1B1B1E"/>
+ </PanelContainer.PanelOverride>
+ <controls:TableContainer Name="EntriesContainer" Columns="4" HorizontalExpand="True" VerticalExpand="True" Margin="5 0">
+ <RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-account'}" HorizontalAlignment="Center"/>
+ <RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-code'}" HorizontalAlignment="Center"/>
+ <RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-balance'}" HorizontalAlignment="Center"/>
+ <RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-cut'}" HorizontalAlignment="Center"/>
+ </controls:TableContainer>
+ </PanelContainer>
+ <BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="5 0">
+ <Button Name="SaveButton" Text="{Loc 'cargo-funding-alloc-console-button-save'}" Disabled="True"/>
+ <RichTextLabel Name="SaveAlertLabel" HorizontalExpand="True" HorizontalAlignment="Right" Visible="False"/>
+ </BoxContainer>
+ </BoxContainer>
+</controls:FancyWindow>
--- /dev/null
+using System.Linq;
+using Content.Client.Message;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Cargo.Components;
+using Content.Shared.Cargo.Prototypes;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+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 FundingAllocationMenu : FancyWindow
+{
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ private readonly EntityQuery<StationBankAccountComponent> _bankQuery;
+
+ public event Action<Dictionary<ProtoId<CargoAccountPrototype>, int>>? OnSavePressed;
+
+ private EntityUid? _station;
+
+ private readonly HashSet<Control> _addedControls = new();
+ private readonly List<SpinBox> _spinBoxes = new();
+ private readonly Dictionary<ProtoId<CargoAccountPrototype>, RichTextLabel> _balanceLabels = new();
+
+ public FundingAllocationMenu()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ _bankQuery = _entityManager.GetEntityQuery<StationBankAccountComponent>();
+
+ SaveButton.OnPressed += _ =>
+ {
+ if (!_entityManager.TryGetComponent<StationBankAccountComponent>(_station, out var bank))
+ return;
+ var accounts = bank.Accounts.Keys.OrderBy(p => p.Id).ToList();
+ var dicts = new Dictionary<ProtoId<CargoAccountPrototype>, int>();
+ for (var i = 0; i< accounts.Count; i++)
+ {
+ dicts.Add(accounts[i], _spinBoxes[i].Value);
+ }
+
+ OnSavePressed?.Invoke(dicts);
+ SaveButton.Disabled = true;
+ };
+
+ BuildEntries();
+ }
+
+ private void BuildEntries()
+ {
+ if (!_entityManager.TryGetComponent<StationBankAccountComponent>(_station, out var bank))
+ return;
+ HelpLabel.Text = Loc.GetString("cargo-funding-alloc-console-label-help",
+ ("percent", (int) (bank.PrimaryCut * 100)));
+
+ foreach (var ctrl in _addedControls)
+ {
+ ctrl.Orphan();
+ }
+
+ _addedControls.Clear();
+ _spinBoxes.Clear();
+ _balanceLabels.Clear();
+
+ var accounts = bank.Accounts.ToList().OrderBy(p => p.Key);
+ foreach (var (account, balance) in accounts)
+ {
+ var accountProto = _prototypeManager.Index(account);
+
+ var accountNameLabel = new RichTextLabel
+ {
+ Modulate = accountProto.Color,
+ Margin = new Thickness(0, 0, 10, 0)
+ };
+ accountNameLabel.SetMarkup($"[bold]{Loc.GetString(accountProto.Name)}[/bold]");
+ EntriesContainer.AddChild(accountNameLabel);
+
+ var codeLabel = new RichTextLabel
+ {
+ Text = $"[font=\"Monospace\"]{Loc.GetString(accountProto.Code)}[/font]",
+ HorizontalAlignment = HAlignment.Center,
+ Margin = new Thickness(5, 0),
+ };
+ EntriesContainer.AddChild(codeLabel);
+
+ var balanceLabel = new RichTextLabel
+ {
+ Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", balance)),
+ HorizontalExpand = true,
+ HorizontalAlignment = HAlignment.Center,
+ Margin = new Thickness(5, 0),
+ };
+ EntriesContainer.AddChild(balanceLabel);
+
+ var box = new SpinBox
+ {
+ HorizontalAlignment = HAlignment.Center,
+ HorizontalExpand = true,
+ Value = (int) (bank.RevenueDistribution[account] * 100),
+ IsValid = val => val is >= 0 and <= 100,
+ };
+ box.ValueChanged += _ => UpdateButtonDisabled();
+ EntriesContainer.AddChild(box);
+
+ _spinBoxes.Add(box);
+ _balanceLabels.Add(account, balanceLabel);
+ _addedControls.Add(accountNameLabel);
+ _addedControls.Add(codeLabel);
+ _addedControls.Add(balanceLabel);
+ _addedControls.Add(box);
+ }
+ }
+
+ private void UpdateButtonDisabled()
+ {
+ if (!_entityManager.TryGetComponent<StationBankAccountComponent>(_station, out var bank))
+ return;
+
+ var sum = _spinBoxes.Sum(s => s.Value);
+ var incorrectSum = sum != 100;
+
+ var differs = false;
+ var accounts = bank.Accounts.Keys.OrderBy(p => p.Id).ToList();
+ for (var i = 0; i < accounts.Count; i++)
+ {
+ var percent = _spinBoxes[i].Value;
+ if (percent != (int) Math.Round(bank.RevenueDistribution[accounts[i]] * 100))
+ {
+ differs = true;
+ break;
+ }
+ }
+
+ SaveButton.Disabled = !differs || incorrectSum;
+
+ var diff = sum - 100;
+ SaveAlertLabel.Visible = incorrectSum;
+ SaveAlertLabel.SetMarkup(Loc.GetString("cargo-funding-alloc-console-label-save-fail",
+ ("pos", Math.Sign(diff)),
+ ("val", Math.Abs(diff))));
+ }
+
+ public void Update(FundingAllocationConsoleBuiState state)
+ {
+ _station = _entityManager.GetEntity(state.Station);
+ BuildEntries();
+ UpdateButtonDisabled();
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ if (!_bankQuery.TryComp(_station, out var bank))
+ return;
+
+ foreach (var (account, label) in _balanceLabels)
+ {
+ label.Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", bank.Accounts[account]));
+ }
+ }
+}
namespace Content.Server.Cargo.Components;
-using Content.Shared.Actions;
-using Robust.Shared.Serialization.TypeSerializers.Implementations;
/// <summary>
/// Any entities intersecting when a shuttle is recalled will be sold.
[RegisterComponent]
[Access(typeof(CargoSystem))]
-public sealed partial class CargoPalletConsoleComponent : Component
-{
- [ViewVariables(VVAccess.ReadWrite), DataField("cashType", customTypeSerializer:typeof(PrototypeIdSerializer<StackPrototype>))]
- public string CashType = "Credit";
-}
+public sealed partial class CargoPalletConsoleComponent : Component;
+++ /dev/null
-using Content.Shared.Cargo;
-
-namespace Content.Server.Cargo.Components;
-
-/// <summary>
-/// Added to the abstract representation of a station to track its money.
-/// </summary>
-[RegisterComponent, Access(typeof(SharedCargoSystem))]
-public sealed partial class StationBankAccountComponent : Component
-{
- [ViewVariables(VVAccess.ReadWrite), DataField("balance")]
- public int Balance = 2000;
-
- /// <summary>
- /// How much the bank balance goes up per second, every Delay period. Rounded down when multiplied.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite), DataField("increasePerSecond")]
- public int IncreasePerSecond = 1;
-}
+using System.Linq;
using Content.Server.Station.Components;
using Content.Shared.Cargo;
using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Prototypes;
using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Cargo.Components;
/// <summary>
/// Maximum amount of orders a station is allowed, approved or not.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite), DataField("capacity")]
+ [DataField]
public int Capacity = 20;
- [ViewVariables(VVAccess.ReadWrite), DataField("orders")]
- public List<CargoOrderData> Orders = new();
+ [ViewVariables]
+ public IEnumerable<CargoOrderData> AllOrders => Orders.SelectMany(p => p.Value);
+
+ [DataField]
+ public Dictionary<ProtoId<CargoAccountPrototype>, List<CargoOrderData>> Orders = new();
/// <summary>
/// Used to determine unique order IDs
/// </summary>
+ [ViewVariables]
public int NumOrdersCreated;
// TODO: Can probably dump this
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSys = default!;
- [Dependency] private readonly IGameTiming _gameTiming = default!;
[ValidatePrototypeId<NameIdentifierGroupPrototype>]
private const string BountyNameIdentifierGroup = "Bounty";
skipped
? CargoBountyHistoryData.BountyResult.Skipped
: CargoBountyHistoryData.BountyResult.Completed,
- _gameTiming.CurTime,
+ _timing.CurTime,
actorName));
ent.Comp.Bounties.RemoveAt(i);
return true;
--- /dev/null
+using System.Linq;
+using Content.Shared.Cargo.Components;
+using Content.Shared.Database;
+using Content.Shared.Emag.Systems;
+using Content.Shared.IdentityManagement;
+using Content.Shared.UserInterface;
+
+namespace Content.Server.Cargo.Systems;
+
+public sealed partial class CargoSystem
+{
+ public void InitializeFunds()
+ {
+ SubscribeLocalEvent<CargoOrderConsoleComponent, CargoConsoleWithdrawFundsMessage>(OnWithdrawFunds);
+ SubscribeLocalEvent<CargoOrderConsoleComponent, CargoConsoleToggleLimitMessage>(OnToggleLimit);
+ SubscribeLocalEvent<FundingAllocationConsoleComponent, SetFundingAllocationBuiMessage>(OnSetFundingAllocation);
+ SubscribeLocalEvent<FundingAllocationConsoleComponent, BeforeActivatableUIOpenEvent>(OnFundAllocationBuiOpen);
+ }
+
+ private void OnWithdrawFunds(Entity<CargoOrderConsoleComponent> ent, ref CargoConsoleWithdrawFundsMessage args)
+ {
+ if (_station.GetOwningStation(ent) is not { } station ||
+ !TryComp<StationBankAccountComponent>(station, out var bank))
+ return;
+
+ if (args.Account == ent.Comp.Account ||
+ args.Amount <= 0 ||
+ args.Amount > GetBalanceFromAccount((station, bank), ent.Comp.Account) * ent.Comp.TransferLimit)
+ return;
+
+ if (_timing.CurTime < ent.Comp.NextAccountActionTime)
+ return;
+
+ if (!_accessReaderSystem.IsAllowed(args.Actor, ent))
+ {
+ ConsolePopup(args.Actor, Loc.GetString("cargo-console-order-not-allowed"));
+ PlayDenySound(ent, ent.Comp);
+ return;
+ }
+
+ ent.Comp.NextAccountActionTime = _timing.CurTime + ent.Comp.AccountActionDelay;
+ Dirty(ent);
+ UpdateBankAccount((station, bank), -args.Amount, CreateAccountDistribution(ent.Comp.Account, bank));
+ _audio.PlayPvs(ApproveSound, ent);
+
+ var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(ent, args.Actor);
+ RaiseLocalEvent(tryGetIdentityShortInfoEvent);
+
+ var ourAccount = _protoMan.Index(ent.Comp.Account);
+ if (args.Account == null)
+ {
+ var stackPrototype = _protoMan.Index(ent.Comp.CashType);
+ _stack.Spawn(args.Amount, stackPrototype, Transform(ent).Coordinates);
+
+ if (!_emag.CheckFlag(ent, EmagType.Interaction))
+ {
+ var msg = Loc.GetString("cargo-console-fund-withdraw-broadcast",
+ ("name", tryGetIdentityShortInfoEvent.Title ?? Loc.GetString("cargo-console-fund-transfer-user-unknown")),
+ ("amount", args.Amount),
+ ("name1", Loc.GetString(ourAccount.Name)),
+ ("code1", Loc.GetString(ourAccount.Code)));
+ _radio.SendRadioMessage(ent, msg, ourAccount.RadioChannel, ent, escapeMarkup: false);
+ }
+ }
+ else
+ {
+ var otherAccount = _protoMan.Index(args.Account.Value);
+ UpdateBankAccount((station, bank), args.Amount, CreateAccountDistribution(args.Account.Value, bank));
+
+ if (!_emag.CheckFlag(ent, EmagType.Interaction))
+ {
+ var msg = Loc.GetString("cargo-console-fund-transfer-broadcast",
+ ("name", tryGetIdentityShortInfoEvent.Title ?? Loc.GetString("cargo-console-fund-transfer-user-unknown")),
+ ("amount", args.Amount),
+ ("name1", Loc.GetString(ourAccount.Name)),
+ ("code1", Loc.GetString(ourAccount.Code)),
+ ("name2", Loc.GetString(otherAccount.Name)),
+ ("code2", Loc.GetString(otherAccount.Code)));
+ _radio.SendRadioMessage(ent, msg, ourAccount.RadioChannel, ent, escapeMarkup: false);
+ _radio.SendRadioMessage(ent, msg, otherAccount.RadioChannel, ent, escapeMarkup: false);
+ }
+ }
+ }
+
+ private void OnToggleLimit(Entity<CargoOrderConsoleComponent> ent, ref CargoConsoleToggleLimitMessage args)
+ {
+ if (!_accessReaderSystem.FindAccessTags(args.Actor).Intersect(ent.Comp.RemoveLimitAccess).Any())
+ {
+ ConsolePopup(args.Actor, Loc.GetString("cargo-console-order-not-allowed"));
+ PlayDenySound(ent, ent.Comp);
+ return;
+ }
+
+ _audio.PlayPvs(ent.Comp.ToggleLimitSound, ent);
+ ent.Comp.TransferUnbounded = !ent.Comp.TransferUnbounded;
+ Dirty(ent);
+ }
+
+
+ private void OnSetFundingAllocation(Entity<FundingAllocationConsoleComponent> ent, ref SetFundingAllocationBuiMessage args)
+ {
+ if (_station.GetOwningStation(ent) is not { } station ||
+ !TryComp<StationBankAccountComponent>(station, out var bank))
+ return;
+
+ if (args.Percents.Count != bank.RevenueDistribution.Count)
+ return;
+
+ var differs = false;
+ foreach (var (account, percent) in args.Percents)
+ {
+ if (percent != (int) Math.Round(bank.RevenueDistribution[account] * 100))
+ {
+ differs = true;
+ break;
+ }
+ }
+
+ if (!differs)
+ return;
+
+ if (args.Percents.Values.Sum() != 100)
+ return;
+
+ bank.RevenueDistribution.Clear();
+ foreach (var (account, percent )in args.Percents)
+ {
+ bank.RevenueDistribution.Add(account, percent / 100.0);
+ }
+ Dirty(station, bank);
+
+ _audio.PlayPvs(ent.Comp.SetDistributionSound, ent);
+ _adminLogger.Add(
+ LogType.Action,
+ LogImpact.Medium,
+ $"{ToPrettyString(args.Actor):player} set station {ToPrettyString(station)} fund distribution: {string.Join(',', bank.RevenueDistribution.Select(p => $"{p.Key}: {p.Value}").ToList())}");
+ }
+
+ private void OnFundAllocationBuiOpen(Entity<FundingAllocationConsoleComponent> ent, ref BeforeActivatableUIOpenEvent args)
+ {
+ if (_station.GetOwningStation(ent) is { } station)
+ _uiSystem.SetUiState(ent.Owner, FundingAllocationConsoleUiKey.Key, new FundingAllocationConsoleBuiState(GetNetEntity(station)));
+ }
+}
using Content.Shared.Interaction;
using Content.Shared.Labels.Components;
using Content.Shared.Paper;
+using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly EmagSystem _emag = default!;
- /// <summary>
- /// How much time to wait (in seconds) before increasing bank accounts balance.
- /// </summary>
- private const int Delay = 10;
-
- /// <summary>
- /// Keeps track of how much time has elapsed since last balance increase.
- /// </summary>
- private float _timer;
-
private void InitializeConsole()
{
SubscribeLocalEvent<CargoOrderConsoleComponent, CargoConsoleAddOrderMessage>(OnAddOrderMessage);
SubscribeLocalEvent<CargoOrderConsoleComponent, BoundUIOpenedEvent>(OnOrderUIOpened);
SubscribeLocalEvent<CargoOrderConsoleComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<CargoOrderConsoleComponent, InteractUsingEvent>(OnInteractUsing);
- SubscribeLocalEvent<CargoOrderConsoleComponent, BankBalanceUpdatedEvent>(OnOrderBalanceUpdated);
SubscribeLocalEvent<CargoOrderConsoleComponent, GotEmaggedEvent>(OnEmagged);
- Reset();
}
private void OnInteractUsing(EntityUid uid, CargoOrderConsoleComponent component, ref InteractUsingEvent args)
if (!TryComp(stationUid, out StationBankAccountComponent? bank))
return;
- _audio.PlayPvs(component.ConfirmSound, uid);
- UpdateBankAccount((stationUid.Value, bank), (int) price);
+ _audio.PlayPvs(ApproveSound, uid);
+ UpdateBankAccount((stationUid.Value, bank), (int) price, CreateAccountDistribution(component.Account, bank));
QueueDel(args.Used);
args.Handled = true;
}
UpdateOrderState(uid, station);
}
- private void Reset()
- {
- _timer = 0;
- }
-
private void OnEmagged(Entity<CargoOrderConsoleComponent> ent, ref GotEmaggedEvent args)
{
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
args.Handled = true;
}
- private void UpdateConsole(float frameTime)
+ private void UpdateConsole()
{
- _timer += frameTime;
-
- // TODO: Doesn't work with serialization and shouldn't just be updating every delay
- // client can just interp this just fine on its own.
- while (_timer > Delay)
+ var stationQuery = EntityQueryEnumerator<StationBankAccountComponent>();
+ while (stationQuery.MoveNext(out var uid, out var bank))
{
- _timer -= Delay;
-
- var stationQuery = EntityQueryEnumerator<StationBankAccountComponent>();
- while (stationQuery.MoveNext(out var uid, out var bank))
- {
- var balanceToAdd = bank.IncreasePerSecond * Delay;
- UpdateBankAccount((uid, bank), balanceToAdd);
- }
-
- var query = EntityQueryEnumerator<CargoOrderConsoleComponent>();
- while (query.MoveNext(out var uid, out var _))
- {
- if (!_uiSystem.IsUiOpen(uid, CargoConsoleUiKey.Orders)) continue;
+ if (_timing.CurTime < bank.NextIncomeTime)
+ continue;
+ bank.NextIncomeTime += bank.IncomeDelay;
- var station = _station.GetOwningStation(uid);
- UpdateOrderState(uid, station);
- }
+ var balanceToAdd = (int) Math.Round(bank.IncreasePerSecond * bank.IncomeDelay.TotalSeconds);
+ UpdateBankAccount((uid, bank), balanceToAdd, bank.RevenueDistribution);
}
}
}
// Find our order again. It might have been dispatched or approved already
- var order = orderDatabase.Orders.Find(order => args.OrderId == order.OrderId && !order.Approved);
+ var order = orderDatabase.Orders[component.Account].Find(order => args.OrderId == order.OrderId && !order.Approved);
if (order == null)
{
return;
return;
}
- var amount = GetOutstandingOrderCount(orderDatabase);
+ var amount = GetOutstandingOrderCount(orderDatabase, component.Account);
var capacity = orderDatabase.Capacity;
// Too many orders, avoid them getting spammed in the UI.
}
var cost = order.Price * order.OrderQuantity;
+ var accountBalance = GetBalanceFromAccount((station.Value, bank), component.Account);
// Not enough balance
- if (cost > bank.Balance)
+ if (cost > accountBalance)
{
ConsolePopup(args.Actor, Loc.GetString("cargo-console-insufficient-funds", ("cost", cost)));
PlayDenySound(uid, component);
if (!ev.Handled)
{
- ev.FulfillmentEntity = TryFulfillOrder((station.Value, stationData), order, orderDatabase);
+ ev.FulfillmentEntity = TryFulfillOrder((station.Value, stationData), component.Account, order, orderDatabase);
if (ev.FulfillmentEntity == null)
{
}
order.Approved = true;
- _audio.PlayPvs(component.ConfirmSound, uid);
+ _audio.PlayPvs(ApproveSound, uid);
if (!_emag.CheckFlag(uid, EmagType.Interaction))
{
("approver", order.Approver ?? string.Empty),
("cost", cost));
_radio.SendRadioMessage(uid, message, component.AnnouncementChannel, uid, escapeMarkup: false);
+ if (CargoOrderConsoleComponent.BaseAnnouncementChannel != component.AnnouncementChannel)
+ _radio.SendRadioMessage(uid, message, CargoOrderConsoleComponent.BaseAnnouncementChannel, uid, escapeMarkup: false);
}
ConsolePopup(args.Actor, Loc.GetString("cargo-console-trade-station", ("destination", MetaData(ev.FulfillmentEntity.Value).EntityName)));
// Log order approval
- _adminLogger.Add(LogType.Action, LogImpact.Low,
- $"{ToPrettyString(player):user} approved order [orderId:{order.OrderId}, quantity:{order.OrderQuantity}, product:{order.ProductId}, requester:{order.Requester}, reason:{order.Reason}] with balance at {bank.Balance}");
+ _adminLogger.Add(LogType.Action,
+ LogImpact.Low,
+ $"{ToPrettyString(player):user} approved order [orderId:{order.OrderId}, quantity:{order.OrderQuantity}, product:{order.ProductId}, requester:{order.Requester}, reason:{order.Reason}] on account {component.Account} with balance at {accountBalance}");
- orderDatabase.Orders.Remove(order);
- UpdateBankAccount((station.Value, bank), -cost);
+ orderDatabase.Orders[component.Account].Remove(order);
+ UpdateBankAccount((station.Value, bank), -cost, CreateAccountDistribution(component.Account, bank));
UpdateOrders(station.Value);
}
- private EntityUid? TryFulfillOrder(Entity<StationDataComponent> stationData, CargoOrderData order, StationCargoOrderDatabaseComponent orderDatabase)
+ private EntityUid? TryFulfillOrder(Entity<StationDataComponent> stationData, ProtoId<CargoAccountPrototype> account, CargoOrderData order, StationCargoOrderDatabaseComponent orderDatabase)
{
// No slots at the trade station
_listEnts.Clear();
{
var coordinates = new EntityCoordinates(trade, pad.Transform.LocalPosition);
- if (FulfillOrder(order, coordinates, orderDatabase.PrinterOutput))
+ if (FulfillOrder(order, account, coordinates, orderDatabase.PrinterOutput))
{
tradeDestination = trade;
order.NumDispatched++;
if (!TryGetOrderDatabase(station, out var orderDatabase))
return;
- RemoveOrder(station.Value, args.OrderId, orderDatabase);
+ RemoveOrder(station.Value, component.Account, args.OrderId, orderDatabase);
}
private void OnAddOrderMessage(EntityUid uid, CargoOrderConsoleComponent component, CargoConsoleAddOrderMessage args)
var data = GetOrderData(args, product, GenerateOrderId(orderDatabase));
- if (!TryAddOrder(stationUid.Value, data, orderDatabase))
+ if (!TryAddOrder(stationUid.Value, component.Account, data, orderDatabase))
{
PlayDenySound(uid, component);
return;
}
// Log order addition
- _adminLogger.Add(LogType.Action, LogImpact.Low,
+ _adminLogger.Add(LogType.Action,
+ LogImpact.Low,
$"{ToPrettyString(player):user} added order [orderId:{data.OrderId}, quantity:{data.OrderQuantity}, product:{data.ProductId}, requester:{data.Requester}, reason:{data.Reason}]");
}
#endregion
-
- private void OnOrderBalanceUpdated(Entity<CargoOrderConsoleComponent> ent, ref BankBalanceUpdatedEvent args)
+ private void UpdateOrderState(EntityUid consoleUid, EntityUid? station)
{
- if (!_uiSystem.IsUiOpen(ent.Owner, CargoConsoleUiKey.Orders))
+ if (!TryComp<CargoOrderConsoleComponent>(consoleUid, out var console))
return;
- UpdateOrderState(ent, args.Station);
- }
-
- private void UpdateOrderState(EntityUid consoleUid, EntityUid? station)
- {
- if (station == null ||
- !TryComp<StationCargoOrderDatabaseComponent>(station, out var orderDatabase) ||
- !TryComp<StationBankAccountComponent>(station, out var bankAccount)) return;
+ if (!TryComp<StationCargoOrderDatabaseComponent>(station, out var orderDatabase))
+ return;
if (_uiSystem.HasUi(consoleUid, CargoConsoleUiKey.Orders))
{
- _uiSystem.SetUiState(consoleUid, CargoConsoleUiKey.Orders, new CargoConsoleInterfaceState(
+ _uiSystem.SetUiState(consoleUid,
+ CargoConsoleUiKey.Orders,
+ new CargoConsoleInterfaceState(
MetaData(station.Value).EntityName,
- GetOutstandingOrderCount(orderDatabase),
+ GetOutstandingOrderCount(orderDatabase, console.Account),
orderDatabase.Capacity,
- bankAccount.Balance,
- orderDatabase.Orders
+ GetNetEntity(station.Value),
+ orderDatabase.Orders[console.Account]
));
}
}
return new CargoOrderData(id, cargoProduct.Product, cargoProduct.Name, cargoProduct.Cost, args.Amount, args.Requester, args.Reason);
}
- public static int GetOutstandingOrderCount(StationCargoOrderDatabaseComponent component)
+ public static int GetOutstandingOrderCount(StationCargoOrderDatabaseComponent component, ProtoId<CargoAccountPrototype> account)
{
var amount = 0;
- foreach (var order in component.Orders)
+ foreach (var order in component.Orders[account])
{
if (!order.Approved)
continue;
string description,
string dest,
StationCargoOrderDatabaseComponent component,
+ ProtoId<CargoAccountPrototype> account,
Entity<StationDataComponent> stationData
)
{
order.Approved = true;
// Log order addition
- _adminLogger.Add(LogType.Action, LogImpact.Low,
+ _adminLogger.Add(LogType.Action,
+ LogImpact.Low,
$"AddAndApproveOrder {description} added order [orderId:{order.OrderId}, quantity:{order.OrderQuantity}, product:{order.ProductId}, requester:{order.Requester}, reason:{order.Reason}]");
// Add it to the list
- return TryAddOrder(dbUid, order, component) && TryFulfillOrder(stationData, order, component).HasValue;
+ return TryAddOrder(dbUid, account, order, component) && TryFulfillOrder(stationData, account, order, component).HasValue;
}
- private bool TryAddOrder(EntityUid dbUid, CargoOrderData data, StationCargoOrderDatabaseComponent component)
+ private bool TryAddOrder(EntityUid dbUid, ProtoId<CargoAccountPrototype> account, CargoOrderData data, StationCargoOrderDatabaseComponent component)
{
- component.Orders.Add(data);
+ component.Orders[account].Add(data);
UpdateOrders(dbUid);
return true;
}
return ++orderDB.NumOrdersCreated;
}
- public void RemoveOrder(EntityUid dbUid, int index, StationCargoOrderDatabaseComponent orderDB)
+ public void RemoveOrder(EntityUid dbUid, ProtoId<CargoAccountPrototype> account, int index, StationCargoOrderDatabaseComponent orderDB)
{
- var sequenceIdx = orderDB.Orders.FindIndex(order => order.OrderId == index);
+ var sequenceIdx = orderDB.Orders[account].FindIndex(order => order.OrderId == index);
if (sequenceIdx != -1)
{
- orderDB.Orders.RemoveAt(sequenceIdx);
+ orderDB.Orders[account].RemoveAt(sequenceIdx);
}
UpdateOrders(dbUid);
}
component.Orders.Clear();
}
- private static bool PopFrontOrder(StationCargoOrderDatabaseComponent orderDB, [NotNullWhen(true)] out CargoOrderData? orderOut)
+ private static bool PopFrontOrder(StationCargoOrderDatabaseComponent orderDB, ProtoId<CargoAccountPrototype> account, [NotNullWhen(true)] out CargoOrderData? orderOut)
{
- var orderIdx = orderDB.Orders.FindIndex(order => order.Approved);
+ var orderIdx = orderDB.Orders[account].FindIndex(order => order.Approved);
if (orderIdx == -1)
{
orderOut = null;
return false;
}
- orderOut = orderDB.Orders[orderIdx];
+ orderOut = orderDB.Orders[account][orderIdx];
orderOut.NumDispatched++;
if (orderOut.NumDispatched >= orderOut.OrderQuantity)
{
// Order is complete. Remove from the queue.
- orderDB.Orders.RemoveAt(orderIdx);
+ orderDB.Orders[account].RemoveAt(orderIdx);
}
return true;
}
/// <summary>
/// Tries to fulfill the next outstanding order.
/// </summary>
- private bool FulfillNextOrder(StationCargoOrderDatabaseComponent orderDB, EntityCoordinates spawn, string? paperProto)
+ [PublicAPI]
+ private bool FulfillNextOrder(StationCargoOrderDatabaseComponent orderDB, ProtoId<CargoAccountPrototype> account, EntityCoordinates spawn, string? paperProto)
{
- if (!PopFrontOrder(orderDB, out var order))
+ if (!PopFrontOrder(orderDB, account, out var order))
return false;
- return FulfillOrder(order, spawn, paperProto);
+ return FulfillOrder(order, account, spawn, paperProto);
}
/// <summary>
/// Fulfills the specified cargo order and spawns paper attached to it.
/// </summary>
- private bool FulfillOrder(CargoOrderData order, EntityCoordinates spawn, string? paperProto)
+ private bool FulfillOrder(CargoOrderData order, ProtoId<CargoAccountPrototype> account, EntityCoordinates spawn, string? paperProto)
{
// Create the item itself
var item = Spawn(order.ProductId, spawn);
var val = Loc.GetString("cargo-console-paper-print-name", ("orderNumber", order.OrderId));
_metaSystem.SetEntityName(printed, val);
- _paperSystem.SetContent((printed, paper), Loc.GetString(
+ var accountProto = _protoMan.Index(account);
+ _paperSystem.SetContent((printed, paper),
+ Loc.GetString(
"cargo-console-paper-print-text",
("orderNumber", order.OrderId),
("itemName", MetaData(item).EntityName),
("orderQuantity", order.OrderQuantity),
("requester", order.Requester),
- ("reason", order.Reason),
- ("approver", order.Approver ?? string.Empty)));
+ ("reason", string.IsNullOrWhiteSpace(order.Reason) ? Loc.GetString("cargo-console-paper-reason-default") : order.Reason),
+ ("account", Loc.GetString(accountProto.Name)),
+ ("accountcode", Loc.GetString(accountProto.Code)),
+ ("approver", string.IsNullOrWhiteSpace(order.Approver) ? Loc.GetString("cargo-console-paper-approver-default") : order.Approver)));
// attempt to attach the label to the item
if (TryComp<PaperLabelComponent>(item, out var label))
+using System.Linq;
using Content.Server.Cargo.Components;
-using Content.Shared.Stacks;
using Content.Shared.Cargo;
using Content.Shared.Cargo.BUI;
using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Events;
-using Content.Shared.GameTicking;
-using Robust.Shared.Map;
-using Robust.Shared.Random;
+using Content.Shared.Cargo.Prototypes;
+using JetBrains.Annotations;
using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
namespace Content.Server.Cargo.Systems;
SubscribeLocalEvent<CargoPalletConsoleComponent, CargoPalletSellMessage>(OnPalletSale);
SubscribeLocalEvent<CargoPalletConsoleComponent, CargoPalletAppraiseMessage>(OnPalletAppraise);
SubscribeLocalEvent<CargoPalletConsoleComponent, BoundUIOpenedEvent>(OnPalletUIOpen);
-
- SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
}
#region Console
+ [PublicAPI]
private void UpdateCargoShuttleConsoles(EntityUid shuttleUid, CargoShuttleComponent _)
{
// Update pilot consoles that are already open.
private void UpdatePalletConsoleInterface(EntityUid uid)
{
- if (Transform(uid).GridUid is not EntityUid gridUid)
+ if (Transform(uid).GridUid is not { } gridUid)
{
- _uiSystem.SetUiState(uid, CargoPalletConsoleUiKey.Sale,
- new CargoPalletConsoleInterfaceState(0, 0, false));
+ _uiSystem.SetUiState(uid,
+ CargoPalletConsoleUiKey.Sale,
+ new CargoPalletConsoleInterfaceState(0, 0, false));
return;
}
- GetPalletGoods(gridUid, out var toSell, out var amount);
- _uiSystem.SetUiState(uid, CargoPalletConsoleUiKey.Sale,
- new CargoPalletConsoleInterfaceState((int) amount, toSell.Count, true));
+ GetPalletGoods(gridUid, out var toSell, out var goods);
+ var totalAmount = goods.Sum(t => t.Item3);
+ _uiSystem.SetUiState(uid,
+ CargoPalletConsoleUiKey.Sale,
+ new CargoPalletConsoleInterfaceState((int) totalAmount, toSell.Count, true));
}
private void OnPalletUIOpen(EntityUid uid, CargoPalletConsoleComponent component, BoundUIOpenedEvent args)
var shuttleName = orderDatabase?.Shuttle != null ? MetaData(orderDatabase.Shuttle.Value).EntityName : string.Empty;
if (_uiSystem.HasUi(uid, CargoConsoleUiKey.Shuttle))
- _uiSystem.SetUiState(uid, CargoConsoleUiKey.Shuttle, new CargoShuttleConsoleBoundUserInterfaceState(
+ {
+ _uiSystem.SetUiState(uid,
+ CargoConsoleUiKey.Shuttle,
+ new CargoShuttleConsoleBoundUserInterfaceState(
station != null ? MetaData(station.Value).EntityName : Loc.GetString("cargo-shuttle-console-station-unknown"),
string.IsNullOrEmpty(shuttleName) ? Loc.GetString("cargo-shuttle-console-shuttle-not-found") : shuttleName,
orders
));
+ }
}
#endregion
return orders;
var spaceRemaining = GetCargoSpace(shuttleUid);
- for (var i = 0; i < component.Orders.Count && spaceRemaining > 0; i++)
+ var allOrders = component.AllOrders.ToList();
+ for (var i = 0; i < allOrders.Count && spaceRemaining > 0; i++)
{
- var order = component.Orders[i];
+ var order = allOrders[i];
if (order.Approved)
{
var numToShip = order.OrderQuantity - order.NumDispatched;
{
// We won't be able to fit the whole order on, so make one
// which represents the space we do have left:
- var reducedOrder = new CargoOrderData(order.OrderId,
- order.ProductId, order.ProductName, order.Price, spaceRemaining, order.Requester, order.Reason);
+ var reducedOrder = new CargoOrderData(
+ order.OrderId,
+ order.ProductId,
+ order.ProductName,
+ order.Price,
+ spaceRemaining,
+ order.Requester,
+ order.Reason);
orders.Add(reducedOrder);
}
else
#region Station
- private bool SellPallets(EntityUid gridUid, out double amount)
+ private bool SellPallets(EntityUid gridUid, out HashSet<(EntityUid, OverrideSellComponent?, double)> goods)
{
- GetPalletGoods(gridUid, out var toSell, out amount);
-
- Log.Debug($"Cargo sold {toSell.Count} entities for {amount}");
+ GetPalletGoods(gridUid, out var toSell, out goods);
if (toSell.Count == 0)
return false;
-
var ev = new EntitySoldEvent(toSell);
RaiseLocalEvent(ref ev);
return true;
}
- private void GetPalletGoods(EntityUid gridUid, out HashSet<EntityUid> toSell, out double amount)
+ private void GetPalletGoods(EntityUid gridUid, out HashSet<EntityUid> toSell, out HashSet<(EntityUid, OverrideSellComponent?, double)> goods)
{
- amount = 0;
+ goods = new HashSet<(EntityUid, OverrideSellComponent?, double)>();
toSell = new HashSet<EntityUid>();
foreach (var (palletUid, _, _) in GetCargoPallets(gridUid, BuySellType.Sell))
// Containers should already get the sell price of their children so can skip those.
_setEnts.Clear();
- _lookup.GetEntitiesIntersecting(palletUid, _setEnts,
+ _lookup.GetEntitiesIntersecting(
+ palletUid,
+ _setEnts,
LookupFlags.Dynamic | LookupFlags.Sundries);
foreach (var ent in _setEnts)
if (price == 0)
continue;
toSell.Add(ent);
- amount += price;
+ goods.Add((ent, CompOrNull<OverrideSellComponent>(ent), price));
}
}
}
{
var xform = Transform(uid);
- if (xform.GridUid is not EntityUid gridUid)
+ if (_station.GetOwningStation(uid) is not { } station ||
+ !TryComp<StationBankAccountComponent>(station, out var bankAccount))
{
- _uiSystem.SetUiState(uid, CargoPalletConsoleUiKey.Sale,
- new CargoPalletConsoleInterfaceState(0, 0, false));
return;
}
- if (!SellPallets(gridUid, out var price))
+ if (xform.GridUid is not { } gridUid)
+ {
+ _uiSystem.SetUiState(uid,
+ CargoPalletConsoleUiKey.Sale,
+ new CargoPalletConsoleInterfaceState(0, 0, false));
+ return;
+ }
+
+ if (!SellPallets(gridUid, out var goods))
return;
- var stackPrototype = _protoMan.Index<StackPrototype>(component.CashType);
- _stack.Spawn((int) price, stackPrototype, xform.Coordinates);
+ var baseDistribution = CreateAccountDistribution(bankAccount.PrimaryAccount, bankAccount, bankAccount.PrimaryCut);
+ foreach (var (_, sellComponent, value) in goods)
+ {
+ Dictionary<ProtoId<CargoAccountPrototype>, double> distribution;
+ if (sellComponent != null)
+ {
+ distribution = new Dictionary<ProtoId<CargoAccountPrototype>, double>()
+ {
+ { sellComponent.OverrideAccount, bankAccount.PrimaryCut },
+ { bankAccount.PrimaryAccount, 1.0 - bankAccount.PrimaryCut },
+ };
+ }
+ else
+ {
+ distribution = baseDistribution;
+ }
+
+ UpdateBankAccount((station, bankAccount), (int) Math.Round(value), distribution, false);
+ }
+
+ Dirty(station, bankAccount);
_audio.PlayPvs(ApproveSound, uid);
UpdatePalletConsoleInterface(uid);
}
#endregion
-
- private void OnRoundRestart(RoundRestartCleanupEvent ev)
- {
- Reset();
- }
}
/// <summary>
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Cargo.Components;
using Content.Server.Power.Components;
continue;
// todo cannot be fucking asked to figure out device linking rn but this shouldn't just default to the first port.
- if (!TryComp<DeviceLinkSinkComponent>(uid, out var sinkComponent) ||
- sinkComponent.LinkedSources.FirstOrNull() is not { } console ||
- console != args.OrderConsole.Owner)
+ if (!TryGetLinkedConsole((uid, tele), out var console) ||
+ console.Value.Owner != args.OrderConsole.Owner)
continue;
for (var i = 0; i < args.Order.OrderQuantity; i++)
}
}
+ private bool TryGetLinkedConsole(Entity<CargoTelepadComponent> ent,
+ [NotNullWhen(true)] out Entity<CargoOrderConsoleComponent>? console)
+ {
+ console = null;
+ if (!TryComp<DeviceLinkSinkComponent>(ent, out var sinkComponent) ||
+ sinkComponent.LinkedSources.FirstOrNull() is not { } linked)
+ return false;
+
+ if (!TryComp<CargoOrderConsoleComponent>(linked, out var consoleComp))
+ return false;
+
+ console = (linked, consoleComp);
+ return true;
+ }
+
+
private void UpdateTelepad(float frameTime)
{
- var query = EntityQueryEnumerator<CargoTelepadComponent>();
- while (query.MoveNext(out var uid, out var comp))
+ var query = EntityQueryEnumerator<CargoTelepadComponent, TransformComponent>();
+ while (query.MoveNext(out var uid, out var comp, out var xform))
{
// Don't EntityQuery for it as it's not required.
TryComp<AppearanceComponent>(uid, out var appearance);
continue;
}
- if (comp.CurrentOrders.Count == 0)
+ if (comp.CurrentOrders.Count == 0 || !TryGetLinkedConsole((uid, comp), out var console))
{
comp.Accumulator += comp.Delay;
continue;
}
- var xform = Transform(uid);
var currentOrder = comp.CurrentOrders.First();
- if (FulfillOrder(currentOrder, xform.Coordinates, comp.PrinterOutput))
+ if (FulfillOrder(currentOrder, console.Value.Comp.Account, xform.Coordinates, comp.PrinterOutput))
{
_audio.PlayPvs(_audio.ResolveSound(comp.TeleportSound), uid, AudioParams.Default.WithVolume(-8f));
!TryComp<StationDataComponent>(station, out var data))
return;
+ if (!TryGetLinkedConsole(ent, out var console))
+ return;
+
foreach (var order in ent.Comp.CurrentOrders)
{
- TryFulfillOrder((station, data), order, db);
+ TryFulfillOrder((station, data), console.Value.Comp.Account, order, db);
}
}
using Content.Server.Radio.EntitySystems;
using Content.Shared.Cargo;
using Content.Shared.Cargo.Components;
+using Content.Shared.Cargo.Prototypes;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Mobs.Components;
using Content.Shared.Paper;
InitializeShuttle();
InitializeTelepad();
InitializeBounty();
+ InitializeFunds();
}
public override void Update(float frameTime)
{
base.Update(frameTime);
- UpdateConsole(frameTime);
+ UpdateConsole();
UpdateTelepad(frameTime);
UpdateBounty();
}
+ /// <summary>
+ /// Adds or removes funds from the <see cref="StationBankAccountComponent"/>.
+ /// </summary>
+ /// <param name="ent">The station.</param>
+ /// <param name="balanceAdded">The amount of funds to add or remove.</param>
+ /// <param name="accountDistribution">The distribution between individual <see cref="CargoAccountPrototype"/>.</param>
+ /// <param name="dirty">Whether to mark the bank accoujnt component as dirty.</param>
[PublicAPI]
- public void UpdateBankAccount(Entity<StationBankAccountComponent?> ent, int balanceAdded)
+ public void UpdateBankAccount(
+ Entity<StationBankAccountComponent?> ent,
+ int balanceAdded,
+ Dictionary<ProtoId<CargoAccountPrototype>, double> accountDistribution,
+ bool dirty = true)
{
if (!Resolve(ent, ref ent.Comp))
return;
- ent.Comp.Balance += balanceAdded;
+ foreach (var (account, percent) in accountDistribution)
+ {
+ var accountBalancedAdded = (int) Math.Round(percent * balanceAdded);
+ ent.Comp.Accounts[account] += accountBalancedAdded;
+ }
- var ev = new BankBalanceUpdatedEvent(ent, ent.Comp.Balance);
+ var ev = new BankBalanceUpdatedEvent(ent, ent.Comp.Accounts);
+ RaiseLocalEvent(ent, ref ev, true);
- var query = EntityQueryEnumerator<BankClientComponent, TransformComponent>();
- while (query.MoveNext(out var client, out var comp, out var xform))
- {
- var station = _station.GetOwningStation(client, xform);
- if (station != ent)
- continue;
+ if (!dirty)
+ return;
- comp.Balance = ent.Comp.Balance;
- Dirty(client, comp);
- RaiseLocalEvent(client, ref ev);
- }
+ Dirty(ent);
}
}
-using Content.Server.Cargo.Components;
using Content.Server.Cargo.Systems;
using Content.Server.Station.Systems;
using Content.Server.StationRecords.Systems;
+using Content.Shared.Cargo.Components;
using Content.Shared.Delivery;
using Content.Shared.FingerprintReader;
using Content.Shared.Labels.EntitySystems;
if (!TryComp<StationBankAccountComponent>(ent.Comp.RecipientStation, out var account))
return;
- _cargo.UpdateBankAccount((ent.Comp.RecipientStation.Value, account), ent.Comp.SpesoReward);
+ _cargo.UpdateBankAccount(
+ (ent.Comp.RecipientStation.Value, account),
+ ent.Comp.SpesoReward,
+ _cargo.CreateAccountDistribution(account.PrimaryAccount, account, account.PrimaryCut));
}
public override void Update(float frameTime)
public EntityUid Spawn(int amount, StackPrototype prototype, EntityCoordinates spawnPosition)
{
// Set the output result parameter to the new stack entity...
- var entity = Spawn(prototype.Spawn, spawnPosition);
+ var entity = SpawnAtPosition(prototype.Spawn, spawnPosition);
var stack = Comp<StackComponent>(entity);
// And finally, set the correct amount!
using Content.Shared.Station.Components;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
+using Robust.Server.GameStates;
using Robust.Server.Player;
using Robust.Shared.Collections;
using Robust.Shared.Configuration;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly MapSystem _map = default!;
+ [Dependency] private readonly PvsOverrideSystem _pvsOverride = default!;
private ISawmill _sawmill = default!;
var metaData = MetaData(uid);
RaiseLocalEvent(new StationInitializedEvent(uid));
_sawmill.Info($"Set up station {metaData.EntityName} ({uid}).");
-
+ _pvsOverride.AddGlobalOverride(uid);
}
private void OnStationDeleted(EntityUid uid, StationDataComponent component, ComponentShutdown args)
[DataField, ViewVariables(VVAccess.ReadWrite)]
public LocId Dest = "cargo-gift-default-dest";
+ /// <summary>
+ /// Account the gifts are deposited into
+ /// </summary>
+ [DataField]
+ public ProtoId<CargoAccountPrototype> Account = "Cargo";
+
/// <summary>
/// Cargo that you would like gifted to the station, with the quantity for each
/// Use Ids from cargoProduct Prototypes
}
// Add some presents
- var outstanding = CargoSystem.GetOutstandingOrderCount(cargoDb);
+ var outstanding = CargoSystem.GetOutstandingOrderCount(cargoDb, component.Account);
while (outstanding < cargoDb.Capacity - component.OrderSpaceToLeave && component.Gifts.Count > 0)
{
// I wish there was a nice way to pop this
Loc.GetString(component.Description),
Loc.GetString(component.Dest),
cargoDb,
+ component.Account,
(station.Value, stationData)
))
{
public string Name;
public int Count;
public int Capacity;
- public int Balance;
+ public NetEntity Station;
public List<CargoOrderData> Orders;
- public CargoConsoleInterfaceState(string name, int count, int capacity, int balance, List<CargoOrderData> orders)
+ public CargoConsoleInterfaceState(string name, int count, int capacity, NetEntity station, List<CargoOrderData> orders)
{
Name = name;
Count = count;
Capacity = capacity;
- Balance = balance;
+ Station = station;
Orders = orders;
}
-}
\ No newline at end of file
+}
+++ /dev/null
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Cargo.Components;
-
-/// <summary>
-/// Makes an entity a client of the station's bank account.
-/// When its balance changes it will have <see cref="BankBalanceUpdatedEvent"/> raised on it.
-/// Other systems can then use this for logic or to update ui states.
-/// </summary>
-[RegisterComponent, NetworkedComponent, Access(typeof(SharedCargoSystem))]
-[AutoGenerateComponentState]
-public sealed partial class BankClientComponent : Component
-{
- /// <summary>
- /// The balance updated for the last station this entity was a part of.
- /// </summary>
- [DataField, AutoNetworkedField]
- public int Balance;
-}
-
-/// <summary>
-/// Raised on an entity with <see cref="BankClientComponent"/> when the bank's balance is updated.
-/// </summary>
-[ByRefEvent]
-public readonly record struct BankBalanceUpdatedEvent(EntityUid Station, int Balance);
+using Content.Shared.Access;
using Content.Shared.Cargo.Prototypes;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Content.Shared.Radio;
+using Content.Shared.Stacks;
using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Cargo.Components;
/// <summary>
/// Handles sending order requests to cargo. Doesn't handle orders themselves via shuttle or telepads.
/// </summary>
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+[Access(typeof(SharedCargoSystem))]
public sealed partial class CargoOrderConsoleComponent : Component
{
- [DataField("soundError")] public SoundSpecifier ErrorSound =
- new SoundPathSpecifier("/Audio/Effects/Cargo/buzz_sigh.ogg");
+ /// <summary>
+ /// The account that this console pulls from for ordering.
+ /// </summary>
+ [DataField]
+ public ProtoId<CargoAccountPrototype> Account = "Cargo";
+
+ [DataField]
+ public SoundSpecifier ErrorSound = new SoundCollectionSpecifier("CargoError");
+
+ /// <summary>
+ /// Sound made when <see cref="TransferUnbounded"/> is toggled.
+ /// </summary>
+ [DataField]
+ public SoundSpecifier ToggleLimitSound = new SoundCollectionSpecifier("CargoToggleLimit");
+
+ /// <summary>
+ /// If true, account transfers have no limit and a lower cooldown.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool TransferUnbounded;
+
+ [ViewVariables]
+ public float TransferLimit => TransferUnbounded ? 1 : BaseTransferLimit;
+
+ /// <summary>
+ /// The maximum percent of total funds that can be transferred or withdrawn in one action.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float BaseTransferLimit = 0.20f;
+
+ /// <summary>
+ /// The time at which account actions can be performed again.
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField]
+ public TimeSpan NextAccountActionTime;
+
+ [ViewVariables]
+ public TimeSpan AccountActionDelay => TransferUnbounded ? UnboundedAccountActionDelay : BaseAccountActionDelay;
+
+ /// <summary>
+ /// The minimum time between account actions when <see cref="TransferUnbounded"/> is false
+ /// </summary>
+ [DataField]
+ public TimeSpan BaseAccountActionDelay = TimeSpan.FromMinutes(1);
- [DataField("soundConfirm")]
- public SoundSpecifier ConfirmSound = new SoundPathSpecifier("/Audio/Effects/Cargo/ping.ogg");
+ /// <summary>
+ /// The minimum time between account actions when <see cref="TransferUnbounded"/> is true
+ /// </summary>
+ [DataField]
+ public TimeSpan UnboundedAccountActionDelay = TimeSpan.FromSeconds(10);
+
+ /// <summary>
+ /// The stack representing cash dispensed on withdrawals.
+ /// </summary>
+ [DataField]
+ public ProtoId<StackPrototype> CashType = "Credit";
/// <summary>
/// All of the <see cref="CargoProductPrototype.Group"/>s that are supported.
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField, AutoNetworkedField]
public List<string> AllowedGroups = new() { "market" };
+ /// <summary>
+ /// Access needed to toggle the limit on this console.
+ /// </summary>
+ [DataField]
+ public HashSet<ProtoId<AccessLevelPrototype>> RemoveLimitAccess = new();
+
/// <summary>
/// Radio channel on which order approval announcements are transmitted
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public ProtoId<RadioChannelPrototype> AnnouncementChannel = "Supply";
+
+ /// <summary>
+ /// Secondary radio channel which always receives order announcements.
+ /// </summary>
+ public static readonly ProtoId<RadioChannelPrototype> BaseAnnouncementChannel = "Supply";
+}
+
+/// <summary>
+/// Withdraw funds from an account
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class CargoConsoleWithdrawFundsMessage : BoundUserInterfaceMessage
+{
+ public ProtoId<CargoAccountPrototype>? Account;
+ public int Amount;
+
+ public CargoConsoleWithdrawFundsMessage(ProtoId<CargoAccountPrototype>? account, int amount)
+ {
+ Account = account;
+ Amount = amount;
+ }
}
+/// <summary>
+/// Toggle the limit on withdrawals and transfers.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class CargoConsoleToggleLimitMessage : BoundUserInterfaceMessage;
--- /dev/null
+using Content.Shared.Cargo.Prototypes;
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Cargo.Components;
+
+/// <summary>
+/// A console that manipulates the distribution of revenue on the station.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedCargoSystem))]
+public sealed partial class FundingAllocationConsoleComponent : Component
+{
+ /// <summary>
+ /// Sound played when the budget distribution is set.
+ /// </summary>
+ [DataField]
+ public SoundSpecifier SetDistributionSound = new SoundCollectionSpecifier("CargoPing");
+}
+
+[Serializable, NetSerializable]
+public sealed class SetFundingAllocationBuiMessage : BoundUserInterfaceMessage
+{
+ public Dictionary<ProtoId<CargoAccountPrototype>, int> Percents;
+
+ public SetFundingAllocationBuiMessage(Dictionary<ProtoId<CargoAccountPrototype>, int> percents)
+ {
+ Percents = percents;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed class FundingAllocationConsoleBuiState : BoundUserInterfaceState
+{
+ public NetEntity Station;
+
+ public FundingAllocationConsoleBuiState(NetEntity station)
+ {
+ Station = station;
+ }
+}
+
+[Serializable, NetSerializable]
+public enum FundingAllocationConsoleUiKey : byte
+{
+ Key
+}
--- /dev/null
+using Content.Shared.Cargo.Prototypes;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Cargo.Components;
+
+/// <summary>
+/// Makes a sellable object portion out its value to a specified department rather than the station default
+/// </summary>
+[RegisterComponent]
+public sealed partial class OverrideSellComponent : Component
+{
+ /// <summary>
+ /// The account that will receive the primary funds from this being sold.
+ /// </summary>
+ [DataField(required: true)]
+ public ProtoId<CargoAccountPrototype> OverrideAccount;
+}
--- /dev/null
+using Content.Shared.Cargo.Prototypes;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Cargo.Components;
+
+/// <summary>
+/// Added to the abstract representation of a station to track its money.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedCargoSystem)), AutoGenerateComponentPause, AutoGenerateComponentState]
+public sealed partial class StationBankAccountComponent : Component
+{
+ /// <summary>
+ /// The account that receives funds by default
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public ProtoId<CargoAccountPrototype> PrimaryAccount = "Cargo";
+
+ /// <summary>
+ /// When giving funds to a particular account, the proportion of funds they should receive compared to remaining accounts.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public double PrimaryCut = 0.75;
+
+ /// <summary>
+ /// A dictionary corresponding to the money held by each cargo account.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public Dictionary<ProtoId<CargoAccountPrototype>, int> Accounts = new()
+ {
+ { "Cargo", 2000 },
+ { "Engineering", 1000 },
+ { "Medical", 1000 },
+ { "Science", 1000 },
+ { "Security", 1000 },
+ { "Service", 1000 },
+ };
+
+ /// <summary>
+ /// A baseline distribution used for income and dispersing leftovers after sale.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public Dictionary<ProtoId<CargoAccountPrototype>, double> RevenueDistribution = new()
+ {
+ { "Cargo", 0.00 },
+ { "Engineering", 0.25 },
+ { "Medical", 0.30 },
+ { "Science", 0.15 },
+ { "Security", 0.20 },
+ { "Service", 0.10 },
+ };
+
+ /// <summary>
+ /// How much the bank balance goes up per second, every Delay period. Rounded down when multiplied.
+ /// </summary>
+ [DataField]
+ public int IncreasePerSecond = 2;
+
+ /// <summary>
+ /// The time at which the station will receive its next deposit of passive income
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
+ public TimeSpan NextIncomeTime;
+
+ /// <summary>
+ /// How much time to wait (in seconds) before increasing bank accounts balance.
+ /// </summary>
+ [DataField]
+ public TimeSpan IncomeDelay = TimeSpan.FromSeconds(50);
+}
+
+/// <summary>
+/// Broadcast and raised on station ent whenever its balance is updated.
+/// </summary>
+[ByRefEvent]
+public readonly record struct BankBalanceUpdatedEvent(EntityUid Station, Dictionary<ProtoId<CargoAccountPrototype>, int> Balance);
--- /dev/null
+using Content.Shared.Radio;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Cargo.Prototypes;
+
+/// <summary>
+/// This is a prototype for a single account that stores money on StationBankAccountComponent
+/// </summary>
+[Prototype]
+public sealed partial class CargoAccountPrototype : IPrototype
+{
+ /// <inheritdoc/>
+ [IdDataField]
+ public string ID { get; } = default!;
+
+ /// <summary>
+ /// Full IC name of the account.
+ /// </summary>
+ [DataField]
+ public LocId Name;
+
+ /// <summary>
+ /// A shortened code used to refer to the account in UIs
+ /// </summary>
+ [DataField]
+ public LocId Code;
+
+ /// <summary>
+ /// Color corresponding to the account.
+ /// </summary>
+ [DataField]
+ public Color Color;
+
+ /// <summary>
+ /// Channel used for announcing transactions.
+ /// </summary>
+ [DataField]
+ public ProtoId<RadioChannelPrototype> RadioChannel;
+}
+using System.Linq;
+using Content.Shared.Cargo.Components;
+using Content.Shared.Cargo.Prototypes;
+using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Cargo;
+public abstract class SharedCargoSystem : EntitySystem
+{
+ /// <summary>
+ /// For a given station, retrieves the balance in a specific account.
+ /// </summary>
+ public int GetBalanceFromAccount(Entity<StationBankAccountComponent?> station, ProtoId<CargoAccountPrototype> account)
+ {
+ if (!Resolve(station, ref station.Comp))
+ return 0;
+
+ return station.Comp.Accounts.GetValueOrDefault(account);
+ }
+
+ /// <summary>
+ /// For a station, creates a distribution between one "primary" account and the other accounts.
+ /// The primary account receives the majority cut specified, with the remaining accounts getting cuts
+ /// distributed through the remaining amount, based on <see cref="StationBankAccountComponent.RevenueDistribution"/>
+ /// </summary>
+ public Dictionary<ProtoId<CargoAccountPrototype>, double> CreateAccountDistribution(
+ ProtoId<CargoAccountPrototype> primary,
+ StationBankAccountComponent stationBank,
+ double primaryCut = 1.0)
+ {
+ var distribution = new Dictionary<ProtoId<CargoAccountPrototype>, double>
+ {
+ { primary, primaryCut }
+ };
+ var remaining = 1.0 - primaryCut;
+
+ var allAccountPercentages = new Dictionary<ProtoId<CargoAccountPrototype>, double>(stationBank.RevenueDistribution);
+ allAccountPercentages.Remove(primary);
+ var weightsSum = allAccountPercentages.Values.Sum();
+
+ foreach (var (account, percentage) in allAccountPercentages)
+ {
+ var adjustedPercentage = percentage / weightsSum;
+
+ distribution.Add(account, remaining * adjustedPercentage);
+ }
+ return distribution;
+ }
+}
+
[NetSerializable, Serializable]
public enum CargoConsoleUiKey : byte
{
Sale
}
-public abstract class SharedCargoSystem : EntitySystem {}
-
[Serializable, NetSerializable]
public enum CargoTelepadState : byte
{
--- /dev/null
+cargo-account-cargo-name = Station Supply Budget
+cargo-account-cargo-code = SUP
+
+cargo-account-engineering-name = Maintenance Savings
+cargo-account-engineering-code = ENG
+
+cargo-account-medical-name = Crew Healthcare Fund
+cargo-account-medical-code = MED
+
+cargo-account-science-name = Interstellar Development Funding
+cargo-account-science-code = RND
+
+cargo-account-security-name = Station Defense Reserves
+cargo-account-security-code = SEC
+
+cargo-account-service-name = Collective Service Holdings
+cargo-account-service-code = SRV
## UI
cargo-console-menu-title = Cargo request console
-cargo-console-menu-account-name-label = Account name:{" "}
+cargo-console-menu-account-name-label = Account:{" "}
cargo-console-menu-account-name-none-text = None
+cargo-console-menu-account-name-format = [bold][color={$color}]{$name}[/color][/bold] [font="Monospace"]\[{$code}\][/font]
cargo-console-menu-shuttle-name-label = Shuttle name:{" "}
cargo-console-menu-shuttle-name-none-text = None
-cargo-console-menu-points-label = Spesos:{" "}
+cargo-console-menu-points-label = Balance:{" "}
cargo-console-menu-points-amount = ${$amount}
cargo-console-menu-shuttle-status-label = Shuttle status:{" "}
cargo-console-menu-shuttle-status-away-text = Away
cargo-console-menu-populate-orders-cargo-order-row-product-name-text = {$productName} (x{$orderAmount}) by {$orderRequester}
cargo-console-menu-cargo-order-row-approve-button = Approve
cargo-console-menu-cargo-order-row-cancel-button = Cancel
+cargo-console-menu-tab-title-orders = Orders
+cargo-console-menu-tab-title-funds = Transfers
+cargo-console-menu-account-action-transfer-limit = [bold]Transfer Limit:[/bold] ${$limit}
+cargo-console-menu-account-action-transfer-limit-unlimited-notifier = [color=gold](Unlimited)[/color]
+cargo-console-menu-account-action-select = [bold]Account Action:[/bold]
+cargo-console-menu-account-action-amount = [bold]Amount:[/bold] $
+cargo-console-menu-account-action-button = Transfer
+cargo-console-menu-toggle-account-lock-button = Toggle Transfer Limit
+cargo-console-menu-account-action-option-withdraw = Withdraw Cash
+cargo-console-menu-account-action-option-transfer = Transfer Funds to {$code}
# Orders
cargo-console-order-not-allowed = Access not allowed
cargo-console-unfulfilled = No room to fulfill order
cargo-console-trade-station = Sent to {$destination}
cargo-console-unlock-approved-order-broadcast = [bold]{$productName} x{$orderAmount}[/bold], which cost [bold]{$cost}[/bold], was approved by [bold]{$approver}[/bold]
+cargo-console-fund-withdraw-broadcast = [bold]{$name} withdrew {$amount} spesos from {$name1} \[{$code1}\]
+cargo-console-fund-transfer-broadcast = [bold]{$name} transferred {$amount} spesos from {$name1} \[{$code1}\] to {$name2} \[{$code2}\][/bold]
+cargo-console-fund-transfer-user-unknown = Unknown
+cargo-console-paper-reason-default = None
+cargo-console-paper-approver-default = Self
cargo-console-paper-print-name = Order #{$orderNumber}
-cargo-console-paper-print-text =
- Order #{$orderNumber}
- Item: {$itemName}
- Quantity: {$orderQuantity}
- Requested by: {$requester}
- Reason: {$reason}
- Approved by: {$approver}
+cargo-console-paper-print-text = [head=2]Order #{$orderNumber}[/head]
+ {"[bold]Item:[/bold]"} {$itemName} (x{$orderQuantity})
+ {"[bold]Requested by:[/bold]"} {$requester}
+
+ {"[head=3]Order Information[/head]"}
+ {"[bold]Payer[/bold]:"} {$account} [font="Monospace"]\[{$accountcode}\][/font]
+ {"[bold]Approved by:[/bold]"} {$approver}
+ {"[bold]Reason:[/bold]"} {$reason}
# Cargo shuttle console
cargo-shuttle-console-menu-title = Cargo shuttle console
cargo-shuttle-console-shuttle-not-found = Not found
cargo-shuttle-console-organics = Detected organic lifeforms on the shuttle
cargo-no-shuttle = No cargo shuttle found!
+
+# Funding allocation console
+cargo-funding-alloc-console-menu-title = Funding Allocation Console
+cargo-funding-alloc-console-label-account = [bold]Account[/bold]
+cargo-funding-alloc-console-label-code = [bold] Code [/bold]
+cargo-funding-alloc-console-label-balance = [bold] Balance [/bold]
+cargo-funding-alloc-console-label-cut = [bold] Revenue Division (%) [/bold]
+
+cargo-funding-alloc-console-label-help = Cargo receives {$percent}% of all profits. The rest is split as specified below:
+cargo-funding-alloc-console-button-save = Save Changes
+cargo-funding-alloc-console-label-save-fail = [bold]Revenue Divisions Invalid![/bold] [color=red]({$pos ->
+ [1] +
+ *[-1] -
+}{$val}%)[/color]
--- /dev/null
+- type: cargoProduct
+ id: CrateLockBoxEngineering
+ icon:
+ sprite: Structures/Storage/Crates/lockbox.rsi
+ state: icon
+ product: CrateLockBoxEngineering
+ cost: 100
+ category: cargoproduct-category-name-engineering
+ group: market
+
+- type: cargoProduct
+ id: CrateLockBoxMedical
+ icon:
+ sprite: Structures/Storage/Crates/lockbox.rsi
+ state: icon
+ product: CrateLockBoxMedical
+ cost: 100
+ category: cargoproduct-category-name-medical
+ group: market
+
+- type: cargoProduct
+ id: CrateLockBoxScience
+ icon:
+ sprite: Structures/Storage/Crates/lockbox.rsi
+ state: icon
+ product: CrateLockBoxScience
+ cost: 100
+ category: cargoproduct-category-name-science
+ group: market
+
+- type: cargoProduct
+ id: CrateLockBoxSecurity
+ icon:
+ sprite: Structures/Storage/Crates/lockbox.rsi
+ state: icon
+ product: CrateLockBoxSecurity
+ cost: 100
+ category: cargoproduct-category-name-security
+ group: market
+
+- type: cargoProduct
+ id: CrateLockBoxService
+ icon:
+ sprite: Structures/Storage/Crates/lockbox.rsi
+ state: icon
+ product: CrateLockBoxService
+ cost: 100
+ category: cargoproduct-category-name-service
+ group: market
- id: DoorRemoteService
- id: HoPIDCard
- id: IDComputerCircuitboard
+ - id: FundingAllocationComputerCircuitboard
+ - id: CargoRequestServiceComputerCircuitboard
- id: RubberStampApproved
- id: RubberStampDenied
- id: RubberStampHop
- id: ClothingHandsGlovesColorYellow
- id: ClothingHeadsetAltEngineering
- id: DoorRemoteEngineering
+ - id: CargoRequestEngineeringComputerCircuitboard
- id: RCD
- id: RCDAmmo
- id: RubberStampCE
- id: HandheldCrewMonitor
- id: Hypospray
- id: MedicalTechFabCircuitboard
+ - id: CargoRequestMedicalComputerCircuitboard
- id: MedkitFilled
- id: RubberStampCMO
- id: MedTekCartridge
- id: HandTeleporter
- id: ProtolatheMachineCircuitboard
- id: ResearchComputerCircuitboard
+ - id: CargoRequestScienceComputerCircuitboard
- id: RubberStampRd
# Hardsuit table, used for suit storage as well
- id: HoloprojectorSecurity
- id: RubberStampHos
- id: SecurityTechFabCircuitboard
+ - id: CargoRequestSecurityComputerCircuitboard
- id: WeaponDisabler
- id: WantedListCartridge
- id: DrinkHosFlask
--- /dev/null
+- type: cargoAccount
+ id: Cargo
+ name: cargo-account-cargo-name
+ code: cargo-account-cargo-code
+ color: "#b48b57"
+ radioChannel: Supply
+
+- type: cargoAccount
+ id: Engineering
+ name: cargo-account-engineering-name
+ code: cargo-account-engineering-code
+ color: "#ff733c"
+ radioChannel: Engineering
+
+- type: cargoAccount
+ id: Medical
+ name: cargo-account-medical-name
+ code: cargo-account-medical-code
+ color: "#57b8f0"
+ radioChannel: Medical
+
+- type: cargoAccount
+ id: Science
+ name: cargo-account-science-name
+ code: cargo-account-science-code
+ color: "#cd7ccd"
+ radioChannel: Science
+
+- type: cargoAccount
+ id: Security
+ name: cargo-account-security-name
+ code: cargo-account-security-code
+ color: "#ff4242"
+ radioChannel: Security
+
+- type: cargoAccount
+ id: Service
+ name: cargo-account-service-name
+ code: cargo-account-service-code
+ color: "#539c00"
+ radioChannel: Service
+
- type: RadarConsole
followEntity: true
- type: CargoOrderConsole
- - type: BankClient
- type: CrewMonitoringConsole
- type: GeneralStationRecordConsole
canDeleteEntries: true
- type: StaticPrice
price: 750
+- type: entity
+ parent: BaseComputerCircuitboard
+ id: CargoRequestEngineeringComputerCircuitboard
+ name: engineering request computer board
+ description: A computer printed circuit board for an engineering request computer.
+ components:
+ - type: Sprite
+ state: cpu_engineering
+ - type: ComputerBoard
+ prototype: ComputerCargoOrdersEngineering
+ - type: StaticPrice
+ price: 750
+
+- type: entity
+ parent: BaseComputerCircuitboard
+ id: CargoRequestMedicalComputerCircuitboard
+ name: medical request computer board
+ description: A computer printed circuit board for a medical request computer.
+ components:
+ - type: Sprite
+ state: cpu_medical
+ - type: ComputerBoard
+ prototype: ComputerCargoOrdersMedical
+ - type: StaticPrice
+ price: 750
+
+- type: entity
+ parent: BaseComputerCircuitboard
+ id: CargoRequestScienceComputerCircuitboard
+ name: science request computer board
+ description: A computer printed circuit board for a science request computer.
+ components:
+ - type: Sprite
+ state: cpu_science
+ - type: ComputerBoard
+ prototype: ComputerCargoOrdersScience
+ - type: StaticPrice
+ price: 750
+
+- type: entity
+ parent: BaseComputerCircuitboard
+ id: CargoRequestSecurityComputerCircuitboard
+ name: security request computer board
+ description: A computer printed circuit board for a security request computer.
+ components:
+ - type: Sprite
+ state: cpu_security
+ - type: ComputerBoard
+ prototype: ComputerCargoOrdersSecurity
+ - type: StaticPrice
+ price: 750
+
+- type: entity
+ parent: BaseComputerCircuitboard
+ id: CargoRequestServiceComputerCircuitboard
+ name: service request computer board
+ description: A computer printed circuit board for a service request computer.
+ components:
+ - type: Sprite
+ state: cpu_service
+ - type: ComputerBoard
+ prototype: ComputerCargoOrdersService
+ - type: StaticPrice
+ price: 750
+
+- type: entity
+ parent: BaseComputerCircuitboard
+ id: FundingAllocationComputerCircuitboard
+ name: funding allocation computer board
+ description: A computer printed circuit board for a funding allocation card console.
+ components:
+ - type: Sprite
+ state: cpu_command
+ - type: ComputerBoard
+ prototype: ComputerFundingAllocation
+ - type: StaticPrice
+ price: 750
+
- type: entity
parent: BaseComputerCircuitboard
id: CargoSaleComputerCircuitboard
tags:
- Write
- type: CargoOrderConsole
- - type: BankClient
+ removeLimitAccess: [ "Quartermaster" ]
- type: ActivatableUI
verbText: qm-clipboard-computer-verb-text
key: enum.CargoConsoleUiKey.Orders
components:
- type: StationBankAccount
- type: StationCargoOrderDatabase
+ orders:
+ Cargo: [ ]
+ Engineering: [ ]
+ Medical: [ ]
+ Science: [ ]
+ Security: [ ]
+ Service: [ ]
- type: StationCargoBountyDatabase
- type: entity
- map: ["computerLayerScreen"]
state: request
- map: ["computerLayerKeys"]
- state: tech_key
+ state: generic_keys
- map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
state: generic_panel_open
- type: CargoOrderConsole
- - type: BankClient
+ removeLimitAccess: [ "Quartermaster" ]
- type: ActiveRadio
channels:
- Supply
guides:
- Cargo
+# Request console variants.
+- type: entity
+ id: ComputerCargoOrdersEngineering
+ parent: ComputerCargoOrders
+ name: engineering request computer
+ description: Used by the engineering department to order supplies.
+ components:
+ - type: Sprite
+ layers:
+ - map: ["computerLayerBody"]
+ state: computer
+ - map: ["computerLayerKeyboard"]
+ state: generic_keyboard
+ - map: ["computerLayerScreen"]
+ state: request-eng
+ - map: ["computerLayerKeys"]
+ state: generic_keys
+ - map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
+ state: generic_panel_open
+ - type: CargoOrderConsole
+ account: Engineering
+ announcementChannel: Engineering
+ removeLimitAccess: [ "ChiefEngineer" ]
+ - type: ActiveRadio
+ channels:
+ - Engineering
+ - type: Computer
+ board: CargoRequestEngineeringComputerCircuitboard
+ - type: PointLight
+ color: "#c9c042"
+ - type: AccessReader
+ access: [["Engineering"]]
+
+- type: entity
+ id: ComputerCargoOrdersMedical
+ parent: ComputerCargoOrders
+ name: medical request computer
+ description: Used by the medical department to order supplies.
+ components:
+ - type: Sprite
+ layers:
+ - map: ["computerLayerBody"]
+ state: computer
+ - map: ["computerLayerKeyboard"]
+ state: generic_keyboard
+ - map: ["computerLayerScreen"]
+ state: request-med
+ - map: ["computerLayerKeys"]
+ state: generic_keys
+ - map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
+ state: generic_panel_open
+ - type: CargoOrderConsole
+ account: Medical
+ announcementChannel: Medical
+ removeLimitAccess: [ "ChiefMedicalOfficer" ]
+ - type: ActiveRadio
+ channels:
+ - Medical
+ - type: Computer
+ board: CargoRequestMedicalComputerCircuitboard
+ - type: PointLight
+ color: "#41e0fc"
+ - type: AccessReader
+ access: [["Medical"]]
+
+- type: entity
+ id: ComputerCargoOrdersScience
+ parent: ComputerCargoOrders
+ name: science request computer
+ description: Used by the science department to order supplies.
+ components:
+ - type: Sprite
+ layers:
+ - map: ["computerLayerBody"]
+ state: computer
+ - map: ["computerLayerKeyboard"]
+ state: generic_keyboard
+ - map: ["computerLayerScreen"]
+ state: request-sci
+ - map: ["computerLayerKeys"]
+ state: generic_keys
+ - map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
+ state: generic_panel_open
+ - type: CargoOrderConsole
+ account: Science
+ announcementChannel: Science
+ removeLimitAccess: [ "ResearchDirector" ]
+ - type: ActiveRadio
+ channels:
+ - Science
+ - type: Computer
+ board: CargoRequestScienceComputerCircuitboard
+ - type: PointLight
+ color: "#b53ca1"
+ - type: AccessReader
+ access: [["Research"]]
+
+- type: entity
+ id: ComputerCargoOrdersSecurity
+ parent: ComputerCargoOrders
+ name: security request computer
+ description: Used by the security department to order supplies.
+ components:
+ - type: Sprite
+ layers:
+ - map: ["computerLayerBody"]
+ state: computer
+ - map: ["computerLayerKeyboard"]
+ state: generic_keyboard
+ - map: ["computerLayerScreen"]
+ state: request-sec
+ - map: ["computerLayerKeys"]
+ state: generic_keys
+ - map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
+ state: generic_panel_open
+ - type: CargoOrderConsole
+ account: Security
+ announcementChannel: Security
+ removeLimitAccess: [ "HeadOfSecurity" ]
+ - type: ActiveRadio
+ channels:
+ - Security
+ - type: Computer
+ board: CargoRequestSecurityComputerCircuitboard
+ - type: PointLight
+ color: "#d11d00"
+ - type: AccessReader
+ access: [["Security"]]
+
+- type: entity
+ id: ComputerCargoOrdersService
+ parent: ComputerCargoOrders
+ name: service request computer
+ description: Used by the service department to order supplies.
+ components:
+ - type: Sprite
+ layers:
+ - map: ["computerLayerBody"]
+ state: computer
+ - map: ["computerLayerKeyboard"]
+ state: generic_keyboard
+ - map: ["computerLayerScreen"]
+ state: request-srv
+ - map: ["computerLayerKeys"]
+ state: generic_keys
+ - map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
+ state: generic_panel_open
+ - type: CargoOrderConsole
+ account: Service
+ announcementChannel: Service
+ removeLimitAccess: [ "HeadOfPersonnel" ]
+ - type: ActiveRadio
+ channels:
+ - Service
+ - type: Computer
+ board: CargoRequestServiceComputerCircuitboard
+ - type: PointLight
+ color: "#afe837"
+ - type: AccessReader
+ access: [["Service"]]
+
- type: entity
id: ComputerCargoBounty
parent: BaseComputerAiAccess
- CargoBounties
- Cargo
+- type: entity
+ parent: BaseComputerAiAccess
+ id: ComputerFundingAllocation
+ name: funding allocation computer
+ description: Terminal for controlling the distribution of funds and pay to departments.
+ components:
+ - type: Sprite
+ layers:
+ - map: ["computerLayerBody"]
+ state: computer
+ - map: ["computerLayerKeyboard"]
+ state: generic_keyboard
+ - map: ["computerLayerScreen"]
+ state: allocate # ALLOCATION !!!
+ - map: ["computerLayerKeys"]
+ state: generic_keys
+ - map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
+ state: generic_panel_open
+ - type: FundingAllocationConsole
+ - type: ActivatableUI
+ key: enum.FundingAllocationConsoleUiKey.Key
+ - type: ActivatableUIRequiresAccess
+ - type: AccessReader
+ access: [["HeadOfPersonnel"]]
+ - type: UserInterface
+ interfaces:
+ enum.FundingAllocationConsoleUiKey.Key:
+ type: FundingAllocationConsoleBoundUserInterface
+ enum.WiresUiKey.Key:
+ type: WiresBoundUserInterface
+ - type: Computer
+ board: FundingAllocationComputerCircuitboard
+ - type: PointLight
+ radius: 1.5
+ energy: 1.6
+ color: "#3c5eb5"
+
- type: entity
parent: BaseComputerAiAccess
id: ComputerCloningConsole
- map: ["computerLayerKeyboard"]
state: generic_keyboard
- map: ["computerLayerScreen"]
- state: request
+ state: transfer
- map: ["computerLayerKeys"]
- state: tech_key
+ state: generic_keys
- map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
state: generic_panel_open
- type: Anchorable
- type: StaticPrice
price: 75
+- type: entity
+ id: CrateBaseLockBox
+ parent: CrateBaseSecure
+ name: lock box
+ description: "A secure lock box. Funds from its sale will be distributed back to the department. Just remember: Cargo always takes a cut."
+ abstract: true
+ components:
+ - type: Sprite
+ sprite: Structures/Storage/Crates/lockbox.rsi
+ - type: GenericVisualizer
+ visuals:
+ enum.PaperLabelVisuals.HasLabel:
+ enum.PaperLabelVisuals.Layer:
+ True: { visible: true }
+ False: { visible: false }
+ enum.PaperLabelVisuals.LabelType:
+ enum.PaperLabelVisuals.Layer:
+ Paper: { state: paper }
+ Bounty: { state: bounty }
+ CaptainsPaper: { state: captains_paper }
+ Invoice: { state: invoice }
+ enum.StorageVisuals.Open:
+ lid_overlay:
+ True: { visible: false }
+ False: { visible: true }
+ - type: OverrideSell
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape:
+ !type:PhysShapeAabb
+ bounds: "-0.3,-0.4,0.3,0.19"
+ density: 50
+ mask:
+ - CrateMask #this is so they can go under plastic flaps
+ layer:
+ - MachineLayer
+
+- type: entity
+ id: CrateLockBoxEngineering
+ parent: CrateBaseLockBox
+ name: engineering lock box
+ components:
+ - type: Sprite
+ layers:
+ - state: base
+ - state: overlay
+ color: "#ad8c27"
+ - state: closed
+ map: ["enum.StorageVisualLayers.Door"]
+ - state: overlay-closed
+ color: "#ad8c27"
+ map: [ lid_overlay ]
+ - state: welded
+ visible: false
+ map: ["enum.WeldableLayers.BaseWelded"]
+ - state: locked
+ map: ["enum.LockVisualLayers.Lock"]
+ shader: unshaded
+ - state: paper
+ sprite: Structures/Storage/Crates/labels.rsi
+ offset: "-0.46875,0.03125"
+ map: ["enum.PaperLabelVisuals.Layer"]
+ - type: OverrideSell
+ overrideAccount: Engineering
+ - type: AccessReader
+ access: [["Engineering"]]
+
+- type: entity
+ id: CrateLockBoxMedical
+ parent: CrateBaseLockBox
+ name: medical lock box
+ components:
+ - type: Sprite
+ layers:
+ - state: base
+ - state: overlay
+ color: "#92c7e8"
+ - state: closed
+ map: ["enum.StorageVisualLayers.Door"]
+ - state: overlay-closed
+ color: "#92c7e8"
+ map: [ lid_overlay ]
+ - state: welded
+ visible: false
+ map: ["enum.WeldableLayers.BaseWelded"]
+ - state: locked
+ map: ["enum.LockVisualLayers.Lock"]
+ shader: unshaded
+ - state: paper
+ sprite: Structures/Storage/Crates/labels.rsi
+ offset: "-0.46875,0.03125"
+ map: ["enum.PaperLabelVisuals.Layer"]
+ - type: OverrideSell
+ overrideAccount: Medical
+ - type: AccessReader
+ access: [["Medical"]]
+
+- type: entity
+ id: CrateLockBoxScience
+ parent: CrateBaseLockBox
+ name: science lock box
+ components:
+ - type: Sprite
+ layers:
+ - state: base
+ - state: overlay
+ color: "#ba4bf0"
+ - state: closed
+ map: ["enum.StorageVisualLayers.Door"]
+ - state: overlay-closed
+ color: "#ba4bf0"
+ map: [ lid_overlay ]
+ - state: welded
+ visible: false
+ map: ["enum.WeldableLayers.BaseWelded"]
+ - state: locked
+ map: ["enum.LockVisualLayers.Lock"]
+ shader: unshaded
+ - state: paper
+ sprite: Structures/Storage/Crates/labels.rsi
+ offset: "-0.46875,0.03125"
+ map: ["enum.PaperLabelVisuals.Layer"]
+ - type: OverrideSell
+ overrideAccount: Science
+ - type: AccessReader
+ access: [["Research"]]
+
+- type: entity
+ id: CrateLockBoxSecurity
+ parent: CrateBaseLockBox
+ name: security lock box
+ components:
+ - type: Sprite
+ layers:
+ - state: base
+ - state: overlay
+ color: "#c12d30"
+ - state: closed
+ map: ["enum.StorageVisualLayers.Door"]
+ - state: overlay-closed
+ color: "#c12d30"
+ map: [ lid_overlay ]
+ - state: welded
+ visible: false
+ map: ["enum.WeldableLayers.BaseWelded"]
+ - state: locked
+ map: ["enum.LockVisualLayers.Lock"]
+ shader: unshaded
+ - state: paper
+ sprite: Structures/Storage/Crates/labels.rsi
+ offset: "-0.46875,0.03125"
+ map: ["enum.PaperLabelVisuals.Layer"]
+ - type: OverrideSell
+ overrideAccount: Security
+ - type: AccessReader
+ access: [["Security"]]
+
+- type: entity
+ id: CrateLockBoxService
+ parent: CrateBaseLockBox
+ name: service lock box
+ components:
+ - type: Sprite
+ layers:
+ - state: base
+ - state: overlay
+ color: "#53b723"
+ - state: closed
+ map: ["enum.StorageVisualLayers.Door"]
+ - state: overlay-closed
+ color: "#53b723"
+ map: [ lid_overlay ]
+ - state: welded
+ visible: false
+ map: ["enum.WeldableLayers.BaseWelded"]
+ - state: locked
+ map: ["enum.LockVisualLayers.Lock"]
+ shader: unshaded
+ - state: paper
+ sprite: Structures/Storage/Crates/labels.rsi
+ offset: "-0.46875,0.03125"
+ map: ["enum.PaperLabelVisuals.Layer"]
+ - type: OverrideSell
+ overrideAccount: Service
+ - type: AccessReader
+ access: [["Service"]]
+
- type: entity
parent: CrateGeneric
id: CratePirate
--- /dev/null
+- type: soundCollection
+ id: CargoPing
+ files:
+ - /Audio/Effects/Cargo/ping.ogg
+
+- type: soundCollection
+ id: CargoError
+ files:
+ - /Audio/Effects/Cargo/buzz_sigh.ogg
+
+- type: soundCollection
+ id: CargoToggleLimit
+ files:
+ - /Audio/Machines/quickbeep.ogg
{
"version": 1,
"license": "CC-BY-SA-3.0",
- "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/bd6873fd4dd6a61d7e46f1d75cd4d90f64c40894. comm_syndie made by Veritius, based on comm. generic_panel_open made by Errant, commit https://github.com/space-wizards/space-station-14/pull/32273, comms_wizard and wizard_key by ScarKy0",
+ "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/bd6873fd4dd6a61d7e46f1d75cd4d90f64c40894. comm_syndie made by Veritius, based on comm. generic_panel_open made by Errant, commit https://github.com/space-wizards/space-station-14/pull/32273, comms_wizard and wizard_key by ScarKy0, request- variants transfer made by EmoGarbage404 (github)",
"size": {
"x": 32,
"y": 32
]
]
},
+ {
+ "name": "allocate",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ [
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ [
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ [
+ 1.0,
+ 1.0,
+ 1.0
+ ]
+ ]
+ },
{
"name": "area_atmos",
"directions": 4,
]
]
},
+ {
+ "name": "request-eng",
+ "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": "request-med",
+ "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": "request-sci",
+ "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": "request-sec",
+ "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": "request-srv",
+ "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": "robot",
"directions": 4
"name": "telesci_key_off",
"directions": 4
},
+ {
+ "name": "transfer",
+ "directions": 4,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
{
"name": "turbinecomp",
"directions": 4
--- /dev/null
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Created by EmoGarbage404 (github) for Space Station 14.",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ },
+ {
+ "name": "base"
+ },
+ {
+ "name": "closed"
+ },
+ {
+ "name": "overlay"
+ },
+ {
+ "name": "overlay-closed"
+ },
+ {
+ "name": "open"
+ },
+ {
+ "name": "welded"
+ },
+ {
+ "name": "locked"
+ },
+ {
+ "name": "unlocked"
+ },
+ {
+ "name": "sparking",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ }
+ ]
+}