Departmental Economy (#36445)
* Cargo Accounts, Request Consoles, and lock boxes * Funding Allocation Computer * final changes * test fix * remove dumb code * ScarKy0 review * first cour * second cour * Update machines.yml * review --------- Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> Co-authored-by: Milon <milonpl.git@proton.me>
This commit is contained in:
@@ -8,15 +8,15 @@ public sealed class CargoConsoleInterfaceState : BoundUserInterfaceState
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
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);
|
||||
@@ -1,33 +1,121 @@
|
||||
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("soundConfirm")]
|
||||
public SoundSpecifier ConfirmSound = new SoundPathSpecifier("/Audio/Effects/Cargo/ping.ogg");
|
||||
[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);
|
||||
|
||||
/// <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;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
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
|
||||
}
|
||||
17
Content.Shared/Cargo/Components/OverrideSellComponent.cs
Normal file
17
Content.Shared/Cargo/Components/OverrideSellComponent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
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);
|
||||
39
Content.Shared/Cargo/Prototypes/CargoAccountPrototype.cs
Normal file
39
Content.Shared/Cargo/Prototypes/CargoAccountPrototype.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,7 +1,54 @@
|
||||
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
|
||||
{
|
||||
@@ -17,8 +64,6 @@ public enum CargoPalletConsoleUiKey : byte
|
||||
Sale
|
||||
}
|
||||
|
||||
public abstract class SharedCargoSystem : EntitySystem {}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum CargoTelepadState : byte
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user