Add cargo shuttle (#8686)
This commit is contained in:
@@ -57,8 +57,6 @@ public sealed partial class AtmosphereSystem
|
||||
mixtures[5].AdjustMoles(Gas.Plasma, Atmospherics.MolesCellGasMiner);
|
||||
mixtures[5].Temperature = 5000f;
|
||||
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
foreach (var gid in args)
|
||||
{
|
||||
if(!EntityUid.TryParse(gid, out var euid))
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
namespace Content.Server.Cargo
|
||||
{
|
||||
public sealed class CargoBankAccount : ICargoBankAccount
|
||||
{
|
||||
[ViewVariables]
|
||||
public int Id { get; }
|
||||
[ViewVariables]
|
||||
public string Name { get; }
|
||||
|
||||
private int _balance;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Balance
|
||||
{
|
||||
get => _balance;
|
||||
set
|
||||
{
|
||||
if (_balance == value)
|
||||
return;
|
||||
_balance = value;
|
||||
OnBalanceChange?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public event Action? OnBalanceChange;
|
||||
|
||||
public CargoBankAccount(int id, string name, int balance)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Balance = balance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.Cargo;
|
||||
|
||||
namespace Content.Server.Cargo
|
||||
{
|
||||
public sealed class CargoOrderDatabase
|
||||
{
|
||||
private readonly Dictionary<int, CargoOrderData> _orders = new();
|
||||
private int _orderNumber = 0;
|
||||
|
||||
public CargoOrderDatabase(int id)
|
||||
{
|
||||
Id = id;
|
||||
CurrentOrderSize = 0;
|
||||
MaxOrderSize = 20;
|
||||
}
|
||||
|
||||
public int Id { get; private set; }
|
||||
public int CurrentOrderSize { get; private set; }
|
||||
public int MaxOrderSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Removes all orders from the database.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_orders.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all orders.
|
||||
/// </summary>
|
||||
/// <returns>A list of orders</returns>
|
||||
public List<CargoOrderData> GetOrders()
|
||||
{
|
||||
return _orders.Values.ToList();
|
||||
}
|
||||
|
||||
public bool TryGetOrder(int id, [NotNullWhen(true)] out CargoOrderData? order)
|
||||
{
|
||||
return _orders.TryGetValue(id, out order);
|
||||
}
|
||||
|
||||
public List<CargoOrderData> SpliceApproved()
|
||||
{
|
||||
var orders = _orders.Values.Where(order => order.Approved).ToList();
|
||||
foreach (var order in orders)
|
||||
_orders.Remove(order.OrderNumber);
|
||||
return orders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an order to the database.
|
||||
/// </summary>
|
||||
/// <param name="requester">The person who requested the item.</param>
|
||||
/// <param name="reason">The reason the product was requested.</param>
|
||||
/// <param name="productId">The ID of the product requested.</param>
|
||||
/// <param name="amount">The amount of the products requested.</param>
|
||||
/// <param name="payingAccountId">The ID of the bank account paying for the order.</param>
|
||||
/// <param name="approved">Whether the order will be bought when the orders are processed.</param>
|
||||
public void AddOrder(string requester, string reason, string productId, int amount, int payingAccountId)
|
||||
{
|
||||
var order = new CargoOrderData(_orderNumber, requester, reason, productId, amount, payingAccountId);
|
||||
if (Contains(order))
|
||||
return;
|
||||
_orders.Add(_orderNumber, order);
|
||||
_orderNumber += 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an order from the database.
|
||||
/// </summary>
|
||||
/// <param name="order">The order to be removed.</param>
|
||||
/// <returns>Whether it could be removed or not</returns>
|
||||
public bool RemoveOrder(int orderNumber)
|
||||
{
|
||||
return _orders.Remove(orderNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Approves an order in the database.
|
||||
/// </summary>
|
||||
/// <param name="order">The order to be approved.</param>
|
||||
public bool ApproveOrder(string approver, int orderNumber)
|
||||
{
|
||||
if (CurrentOrderSize == MaxOrderSize ||
|
||||
!_orders.TryGetValue(orderNumber, out var order) ||
|
||||
order.Approved)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (CurrentOrderSize + order.Amount > MaxOrderSize)
|
||||
{
|
||||
AddOrder(
|
||||
order.Requester,
|
||||
Loc.GetString("cargo-order-database-order-overflow-message", ("placeholder", order.Reason.Replace(" (Overflow)", string.Empty))),
|
||||
order.ProductId,
|
||||
order.Amount - MaxOrderSize - CurrentOrderSize, order.PayingAccountId);
|
||||
|
||||
order.Amount = MaxOrderSize - CurrentOrderSize;
|
||||
}
|
||||
|
||||
order.Approved = true;
|
||||
order.Approver = approver;
|
||||
|
||||
CurrentOrderSize += order.Amount;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the database contains the order or not.
|
||||
/// </summary>
|
||||
/// <param name="order">The order to check</param>
|
||||
/// <returns>Whether the database contained the order or not.</returns>
|
||||
public bool Contains(CargoOrderData order)
|
||||
{
|
||||
return _orders.ContainsValue(order);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current order capacity. This allows more orders to be processed and is invoked after an order is dispatched.
|
||||
/// </summary>
|
||||
public void ClearOrderCapacity()
|
||||
{
|
||||
CurrentOrderSize = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Sound;
|
||||
using Content.Server.MachineLinking.Components;
|
||||
using Content.Shared.MachineLinking;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Cargo.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class CargoConsoleComponent : SharedCargoConsoleComponent
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
private CargoBankAccount? _bankAccount;
|
||||
|
||||
[ViewVariables]
|
||||
public CargoBankAccount? BankAccount
|
||||
{
|
||||
get => _bankAccount;
|
||||
private set
|
||||
{
|
||||
if (_bankAccount == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_bankAccount != null)
|
||||
{
|
||||
_bankAccount.OnBalanceChange -= UpdateUIState;
|
||||
}
|
||||
|
||||
_bankAccount = value;
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
value.OnBalanceChange += UpdateUIState;
|
||||
}
|
||||
|
||||
UpdateUIState();
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("requestOnly")]
|
||||
private bool _requestOnly = false;
|
||||
|
||||
[DataField("errorSound")]
|
||||
private SoundSpecifier _errorSound = new SoundPathSpecifier("/Audio/Effects/error.ogg");
|
||||
|
||||
private bool Powered => !_entMan.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver) || receiver.Powered;
|
||||
private CargoSystem _cargoConsoleSystem = default!;
|
||||
|
||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(CargoConsoleUiKey.Key);
|
||||
|
||||
[DataField("senderPort", customTypeSerializer: typeof(PrototypeIdSerializer<TransmitterPortPrototype>))]
|
||||
public string SenderPort = "OrderSender";
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Owner.EnsureComponentWarn(out CargoOrderDatabaseComponent _);
|
||||
|
||||
if (UserInterface != null)
|
||||
{
|
||||
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
||||
}
|
||||
|
||||
_cargoConsoleSystem = EntitySystem.Get<CargoSystem>();
|
||||
BankAccount = _cargoConsoleSystem.StationAccount;
|
||||
}
|
||||
|
||||
protected override void OnRemove()
|
||||
{
|
||||
if (UserInterface != null)
|
||||
{
|
||||
UserInterface.OnReceiveMessage -= UserInterfaceOnOnReceiveMessage;
|
||||
}
|
||||
|
||||
base.OnRemove();
|
||||
}
|
||||
|
||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg)
|
||||
{
|
||||
if (!_entMan.TryGetComponent(Owner, out CargoOrderDatabaseComponent? orders))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = serverMsg.Message;
|
||||
if (orders.Database == null)
|
||||
return;
|
||||
if (!Powered)
|
||||
return;
|
||||
switch (message)
|
||||
{
|
||||
case CargoConsoleAddOrderMessage msg:
|
||||
{
|
||||
if (msg.Amount <= 0 || _bankAccount == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!_cargoConsoleSystem.AddOrder(orders.Database.Id, msg.Requester, msg.Reason, msg.ProductId,
|
||||
msg.Amount, _bankAccount.Id))
|
||||
{
|
||||
SoundSystem.Play(_errorSound.GetSound(), Filter.Pvs(Owner), Owner, AudioParams.Default);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CargoConsoleRemoveOrderMessage msg:
|
||||
{
|
||||
_cargoConsoleSystem.RemoveOrder(orders.Database.Id, msg.OrderNumber);
|
||||
break;
|
||||
}
|
||||
case CargoConsoleApproveOrderMessage msg:
|
||||
{
|
||||
if (_requestOnly ||
|
||||
!orders.Database.TryGetOrder(msg.OrderNumber, out var order) ||
|
||||
_bankAccount == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (msg.Session.AttachedEntity is not {Valid: true} player)
|
||||
break;
|
||||
|
||||
PrototypeManager.TryIndex(order.ProductId, out CargoProductPrototype? product);
|
||||
if (product == null!)
|
||||
break;
|
||||
var capacity = _cargoConsoleSystem.GetCapacity(orders.Database.Id);
|
||||
if (
|
||||
(capacity.CurrentCapacity == capacity.MaxCapacity
|
||||
|| capacity.CurrentCapacity + order.Amount > capacity.MaxCapacity
|
||||
|| !_cargoConsoleSystem.CheckBalance(_bankAccount.Id, (-product.PointCost) * order.Amount)
|
||||
|| !_cargoConsoleSystem.ApproveOrder(Owner, player, orders.Database.Id, msg.OrderNumber)
|
||||
|| !_cargoConsoleSystem.ChangeBalance(_bankAccount.Id, (-product.PointCost) * order.Amount))
|
||||
)
|
||||
{
|
||||
SoundSystem.Play(_errorSound.GetSound(), Filter.Pvs(Owner), Owner, AudioParams.Default);
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateUIState();
|
||||
break;
|
||||
}
|
||||
case CargoConsoleShuttleMessage _:
|
||||
{
|
||||
// Jesus fucking christ Glass
|
||||
//var approvedOrders = _cargoOrderDataManager.RemoveAndGetApprovedFrom(orders.Database);
|
||||
//orders.Database.ClearOrderCapacity();
|
||||
|
||||
// TODO replace with shuttle code
|
||||
EntityUid? cargoTelepad = null;
|
||||
|
||||
if (_entMan.TryGetComponent<SignalTransmitterComponent>(Owner, out var transmitter) &&
|
||||
transmitter.Outputs.TryGetValue(SenderPort, out var telepad) &&
|
||||
telepad.Count > 0)
|
||||
{
|
||||
// use most recent link
|
||||
var pad = telepad[^1].Uid;
|
||||
if (_entMan.HasComponent<CargoTelepadComponent>(pad) &&
|
||||
_entMan.TryGetComponent<ApcPowerReceiverComponent?>(pad, out var powerReceiver) &&
|
||||
powerReceiver.Powered)
|
||||
cargoTelepad = pad;
|
||||
}
|
||||
|
||||
if (cargoTelepad != null)
|
||||
{
|
||||
if (_entMan.TryGetComponent<CargoTelepadComponent?>(cargoTelepad.Value, out var telepadComponent))
|
||||
{
|
||||
var approvedOrders = _cargoConsoleSystem.RemoveAndGetApprovedOrders(orders.Database.Id);
|
||||
orders.Database.ClearOrderCapacity();
|
||||
foreach (var order in approvedOrders)
|
||||
{
|
||||
_cargoConsoleSystem.QueueTeleport(telepadComponent, order);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUIState()
|
||||
{
|
||||
if (_bankAccount == null || !_entMan.EntityExists(Owner))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var id = _bankAccount.Id;
|
||||
var name = _bankAccount.Name;
|
||||
var balance = _bankAccount.Balance;
|
||||
var capacity = _cargoConsoleSystem.GetCapacity(id);
|
||||
UserInterface?.SetState(new CargoConsoleInterfaceState(_requestOnly, id, name, balance, capacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Content.Shared.Sound;
|
||||
|
||||
namespace Content.Server.Cargo.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles sending order requests to cargo. Doesn't handle orders themselves via shuttle or telepads.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class CargoOrderConsoleComponent : Component
|
||||
{
|
||||
[DataField("soundError")] public SoundSpecifier ErrorSound =
|
||||
new SoundPathSpecifier("/Audio/Effects/Cargo/buzz_sigh.ogg");
|
||||
|
||||
[DataField("soundConfirm")]
|
||||
public SoundSpecifier ConfirmSound = new SoundPathSpecifier("/Audio/Effects/Cargo/ping.ogg");
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Shared.Cargo.Components;
|
||||
|
||||
namespace Content.Server.Cargo.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class CargoOrderDatabaseComponent : SharedCargoOrderDatabaseComponent
|
||||
{
|
||||
public CargoOrderDatabase? Database { get; set; }
|
||||
public bool ConnectedToDatabase => Database != null;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Database = EntitySystem.Get<CargoSystem>().StationOrderDatabase;
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
if (!ConnectedToDatabase)
|
||||
return new CargoOrderDatabaseState(null);
|
||||
return new CargoOrderDatabaseState(Database?.GetOrders());
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Content.Server/Cargo/Components/CargoPalletComponent.cs
Normal file
7
Content.Server/Cargo/Components/CargoPalletComponent.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.Cargo.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Any entities intersecting when a shuttle is recalled will be sold.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class CargoPalletComponent : Component {}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Content.Server.Shuttles.Components;
|
||||
|
||||
namespace Content.Server.Cargo.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Lets you remotely control the cargo shuttle.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class CargoPilotConsoleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ShuttleConsoleComponent"/> that we're proxied into.
|
||||
/// </summary>
|
||||
public EntityUid? Entity;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Content.Shared.Sound;
|
||||
|
||||
namespace Content.Server.Cargo.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class CargoShuttleConsoleComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("soundDeny")]
|
||||
public SoundSpecifier DenySound = new SoundPathSpecifier("/Audio/Effects/Cargo/buzz_two.ogg");
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Content.Server.Cargo.Components
|
||||
public sealed class CargoTelepadComponent : SharedCargoTelepadComponent
|
||||
{
|
||||
[DataField("delay")]
|
||||
public float Delay = 20f;
|
||||
public float Delay = 45f;
|
||||
|
||||
/// <summary>
|
||||
/// How much time we've accumulated until next teleport.
|
||||
@@ -24,9 +24,6 @@ namespace Content.Server.Cargo.Components
|
||||
[DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
|
||||
[ViewVariables]
|
||||
public readonly Stack<CargoOrderData> TeleportQueue = new();
|
||||
|
||||
[ViewVariables]
|
||||
public CargoTelepadState CurrentState = CargoTelepadState.Unpowered;
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
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 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 = 10;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Content.Shared.Cargo;
|
||||
|
||||
namespace Content.Server.Cargo.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Stores all of cargo orders for a particular station.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class StationCargoOrderDatabaseComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum amount of orders a station is allowed, approved or not.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("capacity")]
|
||||
public int Capacity = 20;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("orders")]
|
||||
public Dictionary<int, CargoOrderData> Orders = new();
|
||||
|
||||
/// <summary>
|
||||
/// Tracks the next order index available.
|
||||
/// </summary>
|
||||
public int Index;
|
||||
|
||||
[ViewVariables, DataField("cargoShuttleProto")]
|
||||
public string? CargoShuttleProto = "CargoShuttle";
|
||||
|
||||
/// <summary>
|
||||
/// The cargo shuttle assigned to this station.
|
||||
/// </summary>
|
||||
[ViewVariables, DataField("shuttle")]
|
||||
public EntityUid? Shuttle;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace Content.Server.Cargo
|
||||
{
|
||||
public interface ICargoBankAccount
|
||||
{
|
||||
int Id { get; }
|
||||
string Name { get; }
|
||||
int Balance { get; }
|
||||
public event Action OnBalanceChange;
|
||||
}
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.MachineLinking.System;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.GameTicking;
|
||||
|
||||
namespace Content.Server.Cargo.Systems
|
||||
{
|
||||
public sealed partial class CargoSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// How much time to wait (in seconds) before increasing bank accounts balance.
|
||||
/// </summary>
|
||||
private const float Delay = 10f;
|
||||
/// <summary>
|
||||
/// How many points to give to every bank account every <see cref="Delay"/> seconds.
|
||||
/// </summary>
|
||||
private const int PointIncrease = 150;
|
||||
|
||||
/// <summary>
|
||||
/// Keeps track of how much time has elapsed since last balance increase.
|
||||
/// </summary>
|
||||
private float _timer;
|
||||
/// <summary>
|
||||
/// Stores all bank accounts.
|
||||
/// </summary>
|
||||
private readonly Dictionary<int, CargoBankAccount> _accountsDict = new();
|
||||
|
||||
private readonly Dictionary<int, CargoOrderDatabase> _databasesDict = new();
|
||||
/// <summary>
|
||||
/// Used to assign IDs to bank accounts. Incremental counter.
|
||||
/// </summary>
|
||||
private int _accountIndex;
|
||||
/// <summary>
|
||||
/// Enumeration of all bank accounts.
|
||||
/// </summary>
|
||||
public IEnumerable<CargoBankAccount> BankAccounts => _accountsDict.Values;
|
||||
/// <summary>
|
||||
/// The station's bank account.
|
||||
/// </summary>
|
||||
public CargoBankAccount StationAccount => GetBankAccount(0);
|
||||
|
||||
public CargoOrderDatabase StationOrderDatabase => GetOrderDatabase(0);
|
||||
|
||||
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
|
||||
[Dependency] private readonly SignalLinkerSystem _linker = default!;
|
||||
|
||||
private void InitializeConsole()
|
||||
{
|
||||
SubscribeLocalEvent<CargoConsoleComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
Reset();
|
||||
}
|
||||
private void OnInit(EntityUid uid, CargoConsoleComponent console, ComponentInit args)
|
||||
{
|
||||
_linker.EnsureTransmitterPorts(uid, console.SenderPort);
|
||||
}
|
||||
|
||||
private void Reset(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_accountsDict.Clear();
|
||||
_databasesDict.Clear();
|
||||
_timer = 0;
|
||||
_accountIndex = 0;
|
||||
|
||||
CreateBankAccount("Space Station 14", 1000);
|
||||
CreateOrderDatabase(0);
|
||||
}
|
||||
|
||||
private void UpdateConsole(float frameTime)
|
||||
{
|
||||
_timer += frameTime;
|
||||
|
||||
while (_timer > Delay)
|
||||
{
|
||||
_timer -= Delay;
|
||||
|
||||
foreach (var account in BankAccounts)
|
||||
{
|
||||
account.Balance += PointIncrease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new bank account.
|
||||
/// </summary>
|
||||
public void CreateBankAccount(string name, int balance)
|
||||
{
|
||||
var account = new CargoBankAccount(_accountIndex, name, balance);
|
||||
_accountsDict.Add(_accountIndex, account);
|
||||
_accountIndex += 1;
|
||||
}
|
||||
|
||||
public void CreateOrderDatabase(int id)
|
||||
{
|
||||
_databasesDict.Add(id, new CargoOrderDatabase(id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the bank account associated with the given ID.
|
||||
/// </summary>
|
||||
public CargoBankAccount GetBankAccount(int id)
|
||||
{
|
||||
return _accountsDict[id];
|
||||
}
|
||||
|
||||
public CargoOrderDatabase GetOrderDatabase(int id)
|
||||
{
|
||||
return _databasesDict[id];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the account exists, eventually passing the account in the out parameter.
|
||||
/// </summary>
|
||||
public bool TryGetBankAccount(int id, [NotNullWhen(true)] out CargoBankAccount? account)
|
||||
{
|
||||
return _accountsDict.TryGetValue(id, out account);
|
||||
}
|
||||
|
||||
public bool TryGetOrderDatabase(int id, [NotNullWhen(true)] out CargoOrderDatabase? database)
|
||||
{
|
||||
return _databasesDict.TryGetValue(id, out database);
|
||||
}
|
||||
/// <summary>
|
||||
/// Verifies if there is enough money in the account's balance to pay the amount.
|
||||
/// Returns false if there's no account associated with the given ID
|
||||
/// or if the balance would end up being negative.
|
||||
/// </summary>
|
||||
public bool CheckBalance(int id, int amount)
|
||||
{
|
||||
if (!TryGetBankAccount(id, out var account))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (account.Balance + amount < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/// <summary>
|
||||
/// Attempts to change the given account's balance.
|
||||
/// Returns false if there's no account associated with the given ID
|
||||
/// or if the balance would end up being negative.
|
||||
/// </summary>
|
||||
public bool ChangeBalance(int id, int amount)
|
||||
{
|
||||
if (!TryGetBankAccount(id, out var account))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
account.Balance += amount;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool AddOrder(int id, string requester, string reason, string productId, int amount, int payingAccountId)
|
||||
{
|
||||
if (amount < 1 || !TryGetOrderDatabase(id, out var database) || amount > database.MaxOrderSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
database.AddOrder(requester, reason, productId, amount, payingAccountId);
|
||||
SyncComponentsWithId(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveOrder(int id, int orderNumber)
|
||||
{
|
||||
if (!TryGetOrderDatabase(id, out var database))
|
||||
return false;
|
||||
database.RemoveOrder(orderNumber);
|
||||
SyncComponentsWithId(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ApproveOrder(EntityUid uid, EntityUid approver, int id, int orderNumber, AccessReaderComponent? reader = null)
|
||||
{
|
||||
// does the approver have permission to approve orders?
|
||||
if (Resolve(uid, ref reader) && !_accessReaderSystem.IsAllowed(approver, reader))
|
||||
return false;
|
||||
|
||||
// get the approver's name
|
||||
_idCardSystem.TryFindIdCard(approver, out var idCard);
|
||||
var approverName = idCard?.FullName ?? string.Empty;
|
||||
|
||||
if (!TryGetOrderDatabase(id, out var database))
|
||||
return false;
|
||||
|
||||
if (!database.TryGetOrder(orderNumber, out var order))
|
||||
return false;
|
||||
|
||||
if (!database.ApproveOrder(approverName, orderNumber))
|
||||
return false;
|
||||
|
||||
SyncComponentsWithId(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<CargoOrderData> RemoveAndGetApprovedOrders(int id)
|
||||
{
|
||||
if (!TryGetOrderDatabase(id, out var database))
|
||||
return new List<CargoOrderData>();
|
||||
var approvedOrders = database.SpliceApproved();
|
||||
SyncComponentsWithId(id);
|
||||
return approvedOrders;
|
||||
}
|
||||
|
||||
public (int CurrentCapacity, int MaxCapacity) GetCapacity(int id)
|
||||
{
|
||||
if (!TryGetOrderDatabase(id, out var database))
|
||||
return (0,0);
|
||||
return (database.CurrentOrderSize, database.MaxOrderSize);
|
||||
}
|
||||
|
||||
private void SyncComponentsWithId(int id)
|
||||
{
|
||||
foreach (var comp in EntityManager.EntityQuery<CargoOrderDatabaseComponent>(true))
|
||||
{
|
||||
if (comp.Database == null || comp.Database.Id != id)
|
||||
continue;
|
||||
|
||||
Dirty(comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
324
Content.Server/Cargo/Systems/CargoSystem.Orders.cs
Normal file
324
Content.Server/Cargo/Systems/CargoSystem.Orders.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.MachineLinking.System;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.BUI;
|
||||
using Content.Shared.Cargo.Events;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.GameTicking;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
namespace Content.Server.Cargo.Systems
|
||||
{
|
||||
public sealed partial class CargoSystem
|
||||
{
|
||||
/// <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;
|
||||
|
||||
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
|
||||
[Dependency] private readonly SignalLinkerSystem _linker = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
private void InitializeConsole()
|
||||
{
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, CargoConsoleAddOrderMessage>(OnAddOrderMessage);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, CargoConsoleRemoveOrderMessage>(OnRemoveOrderMessage);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, CargoConsoleApproveOrderMessage>(OnApproveOrderMessage);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, BoundUIOpenedEvent>(OnOrderUIOpened);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
Reset();
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, CargoOrderConsoleComponent orderConsole, ComponentInit args)
|
||||
{
|
||||
var station = _station.GetOwningStation(uid);
|
||||
UpdateOrderState(orderConsole, station);
|
||||
}
|
||||
|
||||
private void Reset(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_timer = 0;
|
||||
}
|
||||
|
||||
private void UpdateConsole(float frameTime)
|
||||
{
|
||||
_timer += frameTime;
|
||||
|
||||
while (_timer > Delay)
|
||||
{
|
||||
_timer -= Delay;
|
||||
|
||||
foreach (var account in EntityQuery<StationBankAccountComponent>())
|
||||
{
|
||||
account.Balance += account.IncreasePerSecond * Delay;
|
||||
}
|
||||
|
||||
foreach (var comp in EntityQuery<CargoOrderConsoleComponent>())
|
||||
{
|
||||
if (!_uiSystem.IsUiOpen(comp.Owner, CargoConsoleUiKey.Orders)) continue;
|
||||
|
||||
var station = _station.GetOwningStation(comp.Owner);
|
||||
UpdateOrderState(comp, station);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Interface
|
||||
|
||||
private void OnApproveOrderMessage(EntityUid uid, CargoOrderConsoleComponent component, CargoConsoleApproveOrderMessage args)
|
||||
{
|
||||
if (args.Session.AttachedEntity is not {Valid: true} player)
|
||||
return;
|
||||
|
||||
if (!_accessReaderSystem.IsAllowed(player, uid))
|
||||
{
|
||||
ConsolePopup(args.Session, Loc.GetString("cargo-console-order-not-allowed"));
|
||||
PlayDenySound(uid, component);
|
||||
return;
|
||||
}
|
||||
|
||||
var orderDatabase = GetOrderDatabase(component);
|
||||
var bankAccount = GetBankAccount(component);
|
||||
|
||||
// No station to deduct from.
|
||||
if (orderDatabase == null || bankAccount == null)
|
||||
{
|
||||
ConsolePopup(args.Session, Loc.GetString("cargo-console-station-not-found"));
|
||||
PlayDenySound(uid, component);
|
||||
return;
|
||||
}
|
||||
|
||||
// No order to approve?
|
||||
if (!orderDatabase.Orders.TryGetValue(args.OrderNumber, out var order) ||
|
||||
order.Approved) return;
|
||||
|
||||
// Invalid order
|
||||
if (!_protoMan.TryIndex<CargoProductPrototype>(order.ProductId, out var product))
|
||||
{
|
||||
ConsolePopup(args.Session, Loc.GetString("cargo-console-invalid-product"));
|
||||
PlayDenySound(uid, component);
|
||||
return;
|
||||
}
|
||||
|
||||
var amount = GetOrderCount(orderDatabase);
|
||||
var capacity = orderDatabase.Capacity;
|
||||
|
||||
// Too many orders, avoid them getting spammed in the UI.
|
||||
if (amount >= capacity)
|
||||
{
|
||||
ConsolePopup(args.Session, Loc.GetString("cargo-console-too-many"));
|
||||
PlayDenySound(uid, component);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cap orders so someone can't spam thousands.
|
||||
var orderAmount = Math.Min(capacity - amount, order.Amount);
|
||||
|
||||
if (orderAmount != order.Amount)
|
||||
{
|
||||
order.Amount = orderAmount;
|
||||
ConsolePopup(args.Session, Loc.GetString("cargo-console-snip-snip"));
|
||||
PlayDenySound(uid, component);
|
||||
}
|
||||
|
||||
var cost = product.PointCost * order.Amount;
|
||||
|
||||
// Not enough balance
|
||||
if (cost > bankAccount.Balance)
|
||||
{
|
||||
ConsolePopup(args.Session, Loc.GetString("cargo-console-insufficient-funds", ("cost", cost)));
|
||||
PlayDenySound(uid, component);
|
||||
return;
|
||||
}
|
||||
|
||||
order.Approved = true;
|
||||
_idCardSystem.TryFindIdCard(player, out var idCard);
|
||||
order.Approver = idCard?.FullName ?? string.Empty;
|
||||
|
||||
|
||||
SoundSystem.Play(component.ConfirmSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid);
|
||||
|
||||
DeductFunds(bankAccount, cost);
|
||||
UpdateOrders(orderDatabase);
|
||||
}
|
||||
|
||||
private void OnRemoveOrderMessage(EntityUid uid, CargoOrderConsoleComponent component, CargoConsoleRemoveOrderMessage args)
|
||||
{
|
||||
var orderDatabase = GetOrderDatabase(component);
|
||||
if (orderDatabase == null) return;
|
||||
RemoveOrder(orderDatabase, args.OrderNumber);
|
||||
}
|
||||
|
||||
private void OnAddOrderMessage(EntityUid uid, CargoOrderConsoleComponent component, CargoConsoleAddOrderMessage args)
|
||||
{
|
||||
if (args.Amount <= 0)
|
||||
return;
|
||||
|
||||
var bank = GetBankAccount(component);
|
||||
if (bank == null) return;
|
||||
var orderDatabase = GetOrderDatabase(component);
|
||||
if (orderDatabase == null) return;
|
||||
|
||||
var data = GetOrderData(args, GetNextIndex(orderDatabase));
|
||||
|
||||
if (!TryAddOrder(orderDatabase, data))
|
||||
{
|
||||
PlayDenySound(uid, component);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOrderUIOpened(EntityUid uid, CargoOrderConsoleComponent component, BoundUIOpenedEvent args)
|
||||
{
|
||||
var station = _station.GetOwningStation(uid);
|
||||
UpdateOrderState(component, station);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void UpdateOrderState(CargoOrderConsoleComponent component, EntityUid? station)
|
||||
{
|
||||
if (station == null ||
|
||||
!TryComp<StationCargoOrderDatabaseComponent>(station, out var orderDatabase) ||
|
||||
!TryComp<StationBankAccountComponent>(station, out var bankAccount)) return;
|
||||
|
||||
var state = new CargoConsoleInterfaceState(
|
||||
MetaData(station.Value).EntityName,
|
||||
GetOrderCount(orderDatabase),
|
||||
orderDatabase.Capacity,
|
||||
bankAccount.Balance,
|
||||
orderDatabase.Orders.Values.ToList());
|
||||
|
||||
_uiSystem.GetUiOrNull(component.Owner, CargoConsoleUiKey.Orders)?.SetState(state);
|
||||
}
|
||||
|
||||
private void ConsolePopup(ICommonSession session, string text)
|
||||
{
|
||||
_popup.PopupCursor(text, Filter.SinglePlayer(session));
|
||||
}
|
||||
|
||||
private void PlayDenySound(EntityUid uid, CargoOrderConsoleComponent component)
|
||||
{
|
||||
SoundSystem.Play(component.ErrorSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid);
|
||||
}
|
||||
|
||||
private CargoOrderData GetOrderData(CargoConsoleAddOrderMessage args, int index)
|
||||
{
|
||||
return new CargoOrderData(index, args.Requester, args.Reason, args.ProductId, args.Amount);
|
||||
}
|
||||
|
||||
private int GetOrderCount(StationCargoOrderDatabaseComponent component)
|
||||
{
|
||||
var amount = 0;
|
||||
|
||||
foreach (var (_, order) in component.Orders)
|
||||
{
|
||||
if (!order.Approved) continue;
|
||||
amount += order.Amount;
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates all of the cargo-related consoles for a particular station.
|
||||
/// This should be called whenever orders change.
|
||||
/// </summary>
|
||||
private void UpdateOrders(StationCargoOrderDatabaseComponent component)
|
||||
{
|
||||
// Order added so all consoles need updating.
|
||||
foreach (var comp in EntityQuery<CargoOrderConsoleComponent>(true))
|
||||
{
|
||||
var station = _station.GetOwningStation(component.Owner);
|
||||
if (station != component.Owner) continue;
|
||||
|
||||
UpdateOrderState(comp, station);
|
||||
}
|
||||
|
||||
foreach (var comp in EntityQuery<CargoShuttleConsoleComponent>(true))
|
||||
{
|
||||
var station = _station.GetOwningStation(component.Owner);
|
||||
if (station != component.Owner) continue;
|
||||
|
||||
UpdateShuttleState(comp, station);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryAddOrder(StationCargoOrderDatabaseComponent component, CargoOrderData data)
|
||||
{
|
||||
component.Orders.Add(data.OrderNumber, data);
|
||||
UpdateOrders(component);
|
||||
return true;
|
||||
}
|
||||
|
||||
private int GetNextIndex(StationCargoOrderDatabaseComponent component)
|
||||
{
|
||||
var index = component.Index;
|
||||
component.Index++;
|
||||
return index;
|
||||
}
|
||||
|
||||
public void RemoveOrder(StationCargoOrderDatabaseComponent component, int index)
|
||||
{
|
||||
if (!component.Orders.Remove(index)) return;
|
||||
UpdateOrders(component);
|
||||
}
|
||||
|
||||
public void ClearOrders(StationCargoOrderDatabaseComponent component)
|
||||
{
|
||||
if (component.Orders.Count == 0) return;
|
||||
|
||||
component.Orders.Clear();
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
private void DeductFunds(StationBankAccountComponent component, int amount)
|
||||
{
|
||||
component.Balance = Math.Max(0, component.Balance - amount);
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
#region Station
|
||||
|
||||
private StationBankAccountComponent? GetBankAccount(CargoOrderConsoleComponent component)
|
||||
{
|
||||
var station = _station.GetOwningStation(component.Owner);
|
||||
|
||||
TryComp<StationBankAccountComponent>(station, out var bankComponent);
|
||||
return bankComponent;
|
||||
}
|
||||
|
||||
private StationCargoOrderDatabaseComponent? GetOrderDatabase(CargoOrderConsoleComponent component)
|
||||
{
|
||||
var station = _station.GetOwningStation(component.Owner);
|
||||
|
||||
TryComp<StationCargoOrderDatabaseComponent>(station, out var orderComponent);
|
||||
return orderComponent;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
528
Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs
Normal file
528
Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs
Normal file
@@ -0,0 +1,528 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.GameTicking.Events;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Shuttles.Events;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.BUI;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Events;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Maps;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
|
||||
public sealed partial class CargoSystem
|
||||
{
|
||||
/*
|
||||
* Handles cargo shuttle mechanics, including cargo shuttle consoles.
|
||||
*/
|
||||
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IMapLoader _loader = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly PricingSystem _pricing = default!;
|
||||
|
||||
public MapId? CargoMap { get; private set; }
|
||||
|
||||
private const float ShuttleRecallRange = 100f;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum mass a grid needs to be to block a shuttle recall.
|
||||
/// </summary>
|
||||
private const float ShuttleCallMassThreshold = 300f;
|
||||
|
||||
private const float CallOffset = 50f;
|
||||
|
||||
private int _index;
|
||||
|
||||
private void InitializeShuttle()
|
||||
{
|
||||
SubscribeLocalEvent<CargoShuttleComponent, MoveEvent>(OnCargoShuttleMove);
|
||||
SubscribeLocalEvent<CargoShuttleConsoleComponent, ComponentStartup>(OnCargoShuttleConsoleStartup);
|
||||
SubscribeLocalEvent<CargoShuttleConsoleComponent, CargoCallShuttleMessage>(OnCargoShuttleCall);
|
||||
SubscribeLocalEvent<CargoShuttleConsoleComponent, CargoRecallShuttleMessage>(RecallCargoShuttle);
|
||||
|
||||
SubscribeLocalEvent<CargoPilotConsoleComponent, ConsoleShuttleEvent>(OnCargoGetConsole);
|
||||
SubscribeLocalEvent<CargoPilotConsoleComponent, AfterActivatableUIOpenEvent>(OnCargoPilotConsoleOpen);
|
||||
SubscribeLocalEvent<CargoPilotConsoleComponent, BoundUIClosedEvent>(OnCargoPilotConsoleClose);
|
||||
|
||||
SubscribeLocalEvent<StationCargoOrderDatabaseComponent, ComponentStartup>(OnCargoOrderStartup);
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
|
||||
}
|
||||
|
||||
#region Cargo Pilot Console
|
||||
|
||||
private void OnCargoPilotConsoleOpen(EntityUid uid, CargoPilotConsoleComponent component, AfterActivatableUIOpenEvent args)
|
||||
{
|
||||
component.Entity = GetShuttleConsole(component);
|
||||
}
|
||||
|
||||
private void OnCargoPilotConsoleClose(EntityUid uid, CargoPilotConsoleComponent component, BoundUIClosedEvent args)
|
||||
{
|
||||
component.Entity = null;
|
||||
}
|
||||
|
||||
private void OnCargoGetConsole(EntityUid uid, CargoPilotConsoleComponent component, ref ConsoleShuttleEvent args)
|
||||
{
|
||||
args.Console = GetShuttleConsole(component);
|
||||
}
|
||||
|
||||
private EntityUid? GetShuttleConsole(CargoPilotConsoleComponent component)
|
||||
{
|
||||
var stationUid = _station.GetOwningStation(component.Owner);
|
||||
|
||||
if (!TryComp<StationCargoOrderDatabaseComponent>(stationUid, out var orderDatabase) ||
|
||||
!TryComp<CargoShuttleComponent>(orderDatabase.Shuttle, out var shuttle)) return null;
|
||||
|
||||
return GetShuttleConsole(shuttle);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Console
|
||||
|
||||
private void UpdateShuttleCargoConsoles(CargoShuttleComponent component)
|
||||
{
|
||||
foreach (var console in EntityQuery<CargoShuttleConsoleComponent>(true))
|
||||
{
|
||||
var stationUid = _station.GetOwningStation(console.Owner);
|
||||
if (stationUid != component.Station) continue;
|
||||
UpdateShuttleState(console, stationUid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCargoShuttleConsoleStartup(EntityUid uid, CargoShuttleConsoleComponent component, ComponentStartup args)
|
||||
{
|
||||
var station = _station.GetOwningStation(uid);
|
||||
UpdateShuttleState(component, station);
|
||||
}
|
||||
|
||||
private void UpdateShuttleState(CargoShuttleConsoleComponent component, EntityUid? station = null)
|
||||
{
|
||||
TryComp<StationCargoOrderDatabaseComponent>(station, out var orderDatabase);
|
||||
TryComp<CargoShuttleComponent>(orderDatabase?.Shuttle, out var shuttle);
|
||||
|
||||
var orders = GetProjectedOrders(orderDatabase, shuttle);
|
||||
var shuttleName = orderDatabase?.Shuttle != null ? MetaData(orderDatabase.Shuttle.Value).EntityName : string.Empty;
|
||||
|
||||
// TODO: Loc
|
||||
_uiSystem.GetUiOrNull(component.Owner, CargoConsoleUiKey.Shuttle)?.SetState(
|
||||
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,
|
||||
CanRecallShuttle(shuttle?.Owner, out _),
|
||||
shuttle?.NextCall,
|
||||
orders));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shuttle
|
||||
|
||||
public EntityUid? GetShuttleConsole(CargoShuttleComponent component)
|
||||
{
|
||||
foreach (var (comp, xform) in EntityQuery<ShuttleConsoleComponent, TransformComponent>(true))
|
||||
{
|
||||
if (xform.ParentUid != component.Owner) continue;
|
||||
return comp.Owner;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void OnCargoShuttleMove(EntityUid uid, CargoShuttleComponent component, ref MoveEvent args)
|
||||
{
|
||||
if (component.Station == null) return;
|
||||
|
||||
var oldCanRecall = component.CanRecall;
|
||||
|
||||
// Check if we can update the recall status.
|
||||
var canRecall = CanRecallShuttle(uid, out _, args.Component);
|
||||
if (oldCanRecall == canRecall) return;
|
||||
|
||||
component.CanRecall = canRecall;
|
||||
_sawmill.Debug($"Updated CanRecall for {ToPrettyString(uid)}");
|
||||
UpdateShuttleCargoConsoles(component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the orders that can fit on the cargo shuttle.
|
||||
/// </summary>
|
||||
private List<CargoOrderData> GetProjectedOrders(
|
||||
StationCargoOrderDatabaseComponent? component = null,
|
||||
CargoShuttleComponent? shuttle = null)
|
||||
{
|
||||
var orders = new List<CargoOrderData>();
|
||||
|
||||
if (component == null || shuttle == null || component.Orders.Count == 0)
|
||||
return orders;
|
||||
|
||||
var space = GetCargoSpace(shuttle);
|
||||
|
||||
if (space == 0) return orders;
|
||||
|
||||
var indices = component.Orders.Keys.ToList();
|
||||
indices.Sort();
|
||||
var amount = 0;
|
||||
|
||||
foreach (var index in indices)
|
||||
{
|
||||
var order = component.Orders[index];
|
||||
if (!order.Approved) continue;
|
||||
|
||||
var cappedAmount = Math.Min(space - amount, order.Amount);
|
||||
amount += cappedAmount;
|
||||
DebugTools.Assert(amount <= space);
|
||||
|
||||
if (cappedAmount < order.Amount)
|
||||
{
|
||||
var reducedOrder = new CargoOrderData(order.OrderNumber, order.Requester, order.Reason, order.ProductId,
|
||||
cappedAmount);
|
||||
|
||||
orders.Add(reducedOrder);
|
||||
break;
|
||||
}
|
||||
|
||||
orders.Add(order);
|
||||
|
||||
if (amount == space) break;
|
||||
}
|
||||
|
||||
return orders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the amount of space the cargo shuttle can fit for orders.
|
||||
/// </summary>
|
||||
private int GetCargoSpace(CargoShuttleComponent component)
|
||||
{
|
||||
var space = GetCargoPallets(component).Count;
|
||||
return space;
|
||||
}
|
||||
|
||||
private List<CargoPalletComponent> GetCargoPallets(CargoShuttleComponent component)
|
||||
{
|
||||
var pads = new List<CargoPalletComponent>();
|
||||
|
||||
foreach (var (comp, compXform) in EntityQuery<CargoPalletComponent, TransformComponent>(true))
|
||||
{
|
||||
if (compXform.ParentUid != component.Owner ||
|
||||
!compXform.Anchored) continue;
|
||||
|
||||
pads.Add(comp);
|
||||
}
|
||||
|
||||
return pads;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Station
|
||||
|
||||
private void OnCargoOrderStartup(EntityUid uid, StationCargoOrderDatabaseComponent component, ComponentStartup args)
|
||||
{
|
||||
// Stations get created first but if any are added at runtime then do this.
|
||||
AddShuttle(component);
|
||||
}
|
||||
|
||||
private void AddShuttle(StationCargoOrderDatabaseComponent component)
|
||||
{
|
||||
Setup();
|
||||
|
||||
if (CargoMap == null || component.Shuttle != null) return;
|
||||
|
||||
if (component.CargoShuttleProto != null)
|
||||
{
|
||||
var prototype = _protoMan.Index<CargoShuttlePrototype>(component.CargoShuttleProto);
|
||||
var possibleNames = _protoMan.Index<DatasetPrototype>(prototype.NameDataset).Values;
|
||||
var name = _random.Pick(possibleNames);
|
||||
|
||||
var (_, gridId) = _loader.LoadBlueprint(CargoMap.Value, prototype.Path.ToString());
|
||||
var shuttleUid = _mapManager.GetGridEuid(gridId!.Value);
|
||||
var xform = Transform(shuttleUid);
|
||||
MetaData(shuttleUid).EntityName = name;
|
||||
|
||||
// TODO: Something better like a bounds check.
|
||||
xform.LocalPosition += 100 * _index;
|
||||
var comp = EnsureComp<CargoShuttleComponent>(shuttleUid);
|
||||
comp.Station = component.Owner;
|
||||
comp.Coordinates = xform.Coordinates;
|
||||
|
||||
component.Shuttle = shuttleUid;
|
||||
comp.NextCall = _timing.CurTime + TimeSpan.FromSeconds(comp.Cooldown);
|
||||
UpdateShuttleCargoConsoles(comp);
|
||||
_index++;
|
||||
_sawmill.Info($"Added cargo shuttle to {ToPrettyString(shuttleUid)}");
|
||||
}
|
||||
}
|
||||
|
||||
private void SellPallets(CargoShuttleComponent component, StationBankAccountComponent bank)
|
||||
{
|
||||
double amount = 0;
|
||||
var toSell = new HashSet<EntityUid>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var pallet in GetCargoPallets(component))
|
||||
{
|
||||
// Containers should already get the sell price of their children so can skip those.
|
||||
foreach (var ent in _lookup.GetEntitiesIntersecting(pallet.Owner, LookupFlags.Anchored))
|
||||
{
|
||||
// Don't re-sell anything, sell anything anchored (e.g. light fixtures), or anything blacklisted
|
||||
// (e.g. players).
|
||||
if (toSell.Contains(ent) ||
|
||||
(xformQuery.TryGetComponent(ent, out var xform) && xform.Anchored)) continue;
|
||||
|
||||
var price = _pricing.GetPrice(ent);
|
||||
if (price == 0) continue;
|
||||
toSell.Add(ent);
|
||||
amount += price;
|
||||
}
|
||||
}
|
||||
|
||||
bank.Balance += (int) amount;
|
||||
_sawmill.Debug($"Cargo sold {toSell.Count} entities for {amount}");
|
||||
|
||||
foreach (var ent in toSell)
|
||||
{
|
||||
Del(ent);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendToCargoMap(EntityUid uid, CargoShuttleComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
component.NextCall = _timing.CurTime + TimeSpan.FromSeconds(component.Cooldown);
|
||||
Transform(uid).Coordinates = component.Coordinates;
|
||||
DebugTools.Assert(MetaData(uid).EntityPaused);
|
||||
|
||||
UpdateShuttleCargoConsoles(component);
|
||||
_sawmill.Info($"Stashed cargo shuttle {ToPrettyString(uid)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a shuttle for delivery.
|
||||
/// </summary>
|
||||
public void CallShuttle(StationCargoOrderDatabaseComponent orderDatabase)
|
||||
{
|
||||
if (!TryComp<CargoShuttleComponent>(orderDatabase.Shuttle, out var shuttle) ||
|
||||
!TryComp<TransformComponent>(orderDatabase.Owner, out var xform)) return;
|
||||
|
||||
// Already called / not available
|
||||
if (shuttle.NextCall == null || _timing.CurTime < shuttle.NextCall)
|
||||
return;
|
||||
|
||||
shuttle.NextCall = null;
|
||||
|
||||
// Find a valid free area nearby to spawn in on
|
||||
var center = new Vector2();
|
||||
var minRadius = 0f;
|
||||
Box2? aabb = null;
|
||||
|
||||
foreach (var grid in _mapManager.GetAllMapGrids(xform.MapID))
|
||||
{
|
||||
aabb = aabb?.Union(grid.WorldAABB) ?? grid.WorldAABB;
|
||||
}
|
||||
|
||||
if (aabb != null)
|
||||
{
|
||||
center = aabb.Value.Center;
|
||||
minRadius = MathF.Max(aabb.Value.Width, aabb.Value.Height);
|
||||
}
|
||||
|
||||
var offset = 0f;
|
||||
if (TryComp<IMapGridComponent>(orderDatabase.Shuttle, out var shuttleGrid))
|
||||
{
|
||||
var bounds = shuttleGrid.Grid.LocalAABB;
|
||||
offset = MathF.Max(bounds.Width, bounds.Height) / 2f;
|
||||
}
|
||||
|
||||
Transform(shuttle.Owner).Coordinates = new EntityCoordinates(xform.ParentUid,
|
||||
center + _random.NextVector2(minRadius + offset, minRadius + CallOffset + offset));
|
||||
DebugTools.Assert(!MetaData(shuttle.Owner).EntityPaused);
|
||||
|
||||
AddCargoContents(shuttle, orderDatabase);
|
||||
UpdateOrders(orderDatabase);
|
||||
UpdateShuttleCargoConsoles(shuttle);
|
||||
|
||||
_sawmill.Info($"Retrieved cargo shuttle {ToPrettyString(shuttle.Owner)} from {ToPrettyString(orderDatabase.Owner)}");
|
||||
}
|
||||
|
||||
private void AddCargoContents(CargoShuttleComponent component, StationCargoOrderDatabaseComponent orderDatabase)
|
||||
{
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var orders = GetProjectedOrders(orderDatabase, component);
|
||||
|
||||
var pads = GetCargoPallets(component);
|
||||
DebugTools.Assert(orders.Sum(o => o.Amount) <= pads.Count);
|
||||
|
||||
for (var i = 0; i < orders.Count; i++)
|
||||
{
|
||||
var order = orders[i];
|
||||
|
||||
Spawn(_protoMan.Index<CargoProductPrototype>(order.ProductId).Product,
|
||||
new EntityCoordinates(component.Owner, xformQuery.GetComponent(_random.PickAndTake(pads).Owner).LocalPosition));
|
||||
order.Amount--;
|
||||
|
||||
if (order.Amount == 0)
|
||||
{
|
||||
orders.RemoveSwap(i);
|
||||
orderDatabase.Orders.Remove(order.OrderNumber);
|
||||
i--;
|
||||
}
|
||||
else
|
||||
{
|
||||
orderDatabase.Orders[order.OrderNumber] = order;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanRecallShuttle(EntityUid? uid, [NotNullWhen(false)] out string? reason, TransformComponent? xform = null)
|
||||
{
|
||||
reason = null;
|
||||
|
||||
if (!TryComp<IMapGridComponent>(uid, out var grid) ||
|
||||
!Resolve(uid.Value, ref xform)) return true;
|
||||
|
||||
var bounds = grid.Grid.WorldAABB.Enlarged(ShuttleRecallRange);
|
||||
var bodyQuery = GetEntityQuery<PhysicsComponent>();
|
||||
|
||||
foreach (var other in _mapManager.FindGridsIntersecting(xform.MapID, bounds))
|
||||
{
|
||||
if (grid.GridIndex == other.Index ||
|
||||
!bodyQuery.TryGetComponent(other.GridEntityId, out var body) ||
|
||||
body.Mass < ShuttleCallMassThreshold) continue;
|
||||
|
||||
reason = Loc.GetString("cargo-shuttle-console-proximity");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RecallCargoShuttle(EntityUid uid, CargoShuttleConsoleComponent component, CargoRecallShuttleMessage args)
|
||||
{
|
||||
var player = args.Session.AttachedEntity;
|
||||
|
||||
if (player == null) return;
|
||||
|
||||
var stationUid = _station.GetOwningStation(component.Owner);
|
||||
|
||||
if (!TryComp<StationCargoOrderDatabaseComponent>(stationUid, out var orderDatabase) ||
|
||||
!TryComp<StationBankAccountComponent>(stationUid, out var bank)) return;
|
||||
|
||||
if (!TryComp<CargoShuttleComponent>(orderDatabase.Shuttle, out var shuttle))
|
||||
{
|
||||
_popup.PopupEntity($"No cargo shuttle found!", args.Entity, Filter.Entities(args.Entity));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CanRecallShuttle(shuttle.Owner, out var reason))
|
||||
{
|
||||
_popup.PopupEntity(reason, args.Entity, Filter.Entities(args.Entity));
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsBlocked(shuttle))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cargo-shuttle-console-organics"), player.Value, Filter.Entities(player.Value));
|
||||
SoundSystem.Play(component.DenySound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid);
|
||||
return;
|
||||
};
|
||||
|
||||
SellPallets(shuttle, bank);
|
||||
|
||||
SendToCargoMap(orderDatabase.Shuttle.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="component"></param>
|
||||
private bool IsBlocked(CargoShuttleComponent component)
|
||||
{
|
||||
// TODO: Would be good to rate-limit this on the console.
|
||||
var mobQuery = GetEntityQuery<MobStateComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
return FoundOrganics(component.Owner, mobQuery, xformQuery);
|
||||
}
|
||||
|
||||
private bool FoundOrganics(EntityUid uid, EntityQuery<MobStateComponent> mobQuery, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
var xform = xformQuery.GetComponent(uid);
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
if (mobQuery.HasComponent(child.Value) ||
|
||||
FoundOrganics(child.Value, mobQuery, xformQuery)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnCargoShuttleCall(EntityUid uid, CargoShuttleConsoleComponent component, CargoCallShuttleMessage args)
|
||||
{
|
||||
var stationUid = _station.GetOwningStation(args.Entity);
|
||||
if (!TryComp<StationCargoOrderDatabaseComponent>(stationUid, out var orderDatabase)) return;
|
||||
CallShuttle(orderDatabase);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void OnRoundRestart(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
private void Cleanup()
|
||||
{
|
||||
if (CargoMap == null || !_mapManager.MapExists(CargoMap.Value))
|
||||
{
|
||||
CargoMap = null;
|
||||
DebugTools.Assert(!EntityQuery<CargoShuttleComponent>().Any());
|
||||
return;
|
||||
}
|
||||
|
||||
_mapManager.DeleteMap(CargoMap.Value);
|
||||
CargoMap = null;
|
||||
|
||||
// Shuttle may not have been in the cargo dimension (e.g. on the station map) so need to delete.
|
||||
foreach (var comp in EntityQuery<CargoShuttleComponent>())
|
||||
{
|
||||
QueueDel(comp.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private void Setup()
|
||||
{
|
||||
if (CargoMap != null && _mapManager.MapExists(CargoMap.Value)) return;
|
||||
|
||||
// It gets mapinit which is okay... buuutt we still want it paused to avoid power draining.
|
||||
CargoMap = _mapManager.CreateMap();
|
||||
_mapManager.SetMapPaused(CargoMap!.Value, true);
|
||||
|
||||
foreach (var comp in EntityQuery<StationCargoOrderDatabaseComponent>(true))
|
||||
{
|
||||
AddShuttle(comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Labels.Components;
|
||||
using Content.Server.Paper;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
@@ -27,7 +30,7 @@ public sealed partial class CargoSystem
|
||||
// Don't EntityQuery for it as it's not required.
|
||||
TryComp<AppearanceComponent>(comp.Owner, out var appearance);
|
||||
|
||||
if (comp.CurrentState == CargoTelepadState.Unpowered || comp.TeleportQueue.Count <= 0)
|
||||
if (comp.CurrentState == CargoTelepadState.Unpowered)
|
||||
{
|
||||
comp.CurrentState = CargoTelepadState.Idle;
|
||||
appearance?.SetData(CargoTelepadVisuals.State, CargoTelepadState.Idle);
|
||||
@@ -45,14 +48,44 @@ public sealed partial class CargoSystem
|
||||
continue;
|
||||
}
|
||||
|
||||
var product = comp.TeleportQueue.Pop();
|
||||
var station = _station.GetOwningStation(comp.Owner);
|
||||
|
||||
if (!TryComp<StationCargoOrderDatabaseComponent>(station, out var orderDatabase) ||
|
||||
orderDatabase.Orders.Count == 0)
|
||||
{
|
||||
comp.Accumulator += comp.Delay;
|
||||
continue;
|
||||
}
|
||||
|
||||
var orderIndices = new ValueList<int>();
|
||||
|
||||
foreach (var (oIndex, oOrder) in orderDatabase.Orders)
|
||||
{
|
||||
if (!oOrder.Approved) continue;
|
||||
orderIndices.Add(oIndex);
|
||||
}
|
||||
|
||||
if (orderIndices.Count == 0)
|
||||
{
|
||||
comp.Accumulator += comp.Delay;
|
||||
continue;
|
||||
}
|
||||
|
||||
orderIndices.Sort();
|
||||
var index = orderIndices[0];
|
||||
var order = orderDatabase.Orders[index];
|
||||
order.Amount--;
|
||||
|
||||
if (order.Amount <= 0)
|
||||
orderDatabase.Orders.Remove(index);
|
||||
|
||||
SoundSystem.Play(comp.TeleportSound.GetSound(), Filter.Pvs(comp.Owner), comp.Owner, AudioParams.Default.WithVolume(-8f));
|
||||
SpawnProduct(comp, product);
|
||||
SpawnProduct(comp, order);
|
||||
UpdateOrders(orderDatabase);
|
||||
|
||||
comp.CurrentState = CargoTelepadState.Teleporting;
|
||||
appearance?.SetData(CargoTelepadVisuals.State, CargoTelepadState.Teleporting);
|
||||
comp.Accumulator = comp.Delay;
|
||||
comp.Accumulator += comp.Delay;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,14 +120,6 @@ public sealed partial class CargoSystem
|
||||
SetEnabled(component);
|
||||
}
|
||||
|
||||
public void QueueTeleport(CargoTelepadComponent component, CargoOrderData order)
|
||||
{
|
||||
for (var i = 0; i < order.Amount; i++)
|
||||
{
|
||||
component.TeleportQueue.Push(order);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawn the product and a piece of paper. Attempt to attach the paper to the product.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -9,11 +12,28 @@ public sealed partial class CargoSystem : SharedCargoSystem
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _slots = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_sawmill = Logger.GetSawmill("cargo");
|
||||
InitializeConsole();
|
||||
InitializeShuttle();
|
||||
InitializeTelepad();
|
||||
SubscribeLocalEvent<StationInitializedEvent>(OnStationInit);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
private void OnStationInit(StationInitializedEvent ev)
|
||||
{
|
||||
EnsureComp<StationBankAccountComponent>(ev.Station);
|
||||
EnsureComp<StationCargoOrderDatabaseComponent>(ev.Station);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
|
||||
@@ -187,17 +187,26 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
|
||||
// TODO: Make this a prototype
|
||||
var map = "/Maps/infiltrator.yml";
|
||||
|
||||
var aabbs = _stationSystem.Stations.SelectMany(x =>
|
||||
Comp<StationDataComponent>(x).Grids.Select(x => _mapManager.GetGridComp(x).Grid.WorldAABB)).ToArray();
|
||||
var aabb = aabbs[0];
|
||||
for (int i = 1; i < aabbs.Length; i++)
|
||||
var center = new Vector2();
|
||||
var minRadius = 0f;
|
||||
Box2? aabb = null;
|
||||
|
||||
foreach (var uid in _stationSystem.Stations)
|
||||
{
|
||||
aabb.Union(aabbs[i]);
|
||||
var grid = Comp<IMapGridComponent>(uid).Grid;
|
||||
|
||||
aabb = aabb?.Union(grid.WorldAABB) ?? grid.WorldAABB;
|
||||
}
|
||||
|
||||
if (aabb != null)
|
||||
{
|
||||
center = aabb.Value.Center;
|
||||
minRadius = MathF.Max(aabb.Value.Width, aabb.Value.Height);
|
||||
}
|
||||
|
||||
var (_, gridId) = _mapLoader.LoadBlueprint(GameTicker.DefaultMap, map, new MapLoadOptions
|
||||
{
|
||||
Offset = aabb.Center + MathF.Max(aabb.Height / 2f, aabb.Width / 2f) * 2.5f
|
||||
Offset = center + MathF.Max(minRadius, minRadius) + 1000f,
|
||||
});
|
||||
|
||||
if (!gridId.HasValue)
|
||||
|
||||
@@ -42,6 +42,9 @@ namespace Content.Server.Gravity.EntitySystems
|
||||
|
||||
if (_mapManager.TryGetGrid(xform.GridUid, out var grid))
|
||||
{
|
||||
var alerts = _alerts.GetOrNew(xform.GridUid.Value);
|
||||
alerts.Add(status);
|
||||
|
||||
if (EntityManager.GetComponent<GravityComponent>(grid.GridEntityId).Enabled)
|
||||
{
|
||||
RemoveWeightless(status.Owner);
|
||||
@@ -51,6 +54,10 @@ namespace Content.Server.Gravity.EntitySystems
|
||||
AddWeightless(status.Owner);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddWeightless(status.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAlert(AlertsComponent status)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Shared.Vehicle.Components;
|
||||
@@ -61,9 +62,18 @@ namespace Content.Server.Physics.Controllers
|
||||
var newPilots = new Dictionary<ShuttleComponent, List<(PilotComponent, IMoverComponent)>>();
|
||||
|
||||
// We just mark off their movement and the shuttle itself does its own movement
|
||||
foreach (var (pilot, mover, xform) in EntityManager.EntityQuery<PilotComponent, SharedPlayerInputMoverComponent, TransformComponent>())
|
||||
foreach (var (pilot, mover) in EntityManager.EntityQuery<PilotComponent, SharedPlayerInputMoverComponent>())
|
||||
{
|
||||
if (pilot.Console == null) continue;
|
||||
var consoleEnt = pilot.Console?.Owner;
|
||||
|
||||
// TODO: This is terrible. Just make a new mover and also make it remote piloting + device networks
|
||||
if (TryComp<CargoPilotConsoleComponent>(consoleEnt, out var cargoConsole))
|
||||
{
|
||||
consoleEnt = cargoConsole.Entity;
|
||||
}
|
||||
|
||||
if (!TryComp<TransformComponent>(consoleEnt, out var xform)) continue;
|
||||
|
||||
_excludedMobs.Add(mover.Owner);
|
||||
|
||||
var gridId = xform.GridUid;
|
||||
|
||||
12
Content.Server/Shuttles/Events/ConsoleShuttleEvent.cs
Normal file
12
Content.Server/Shuttles/Events/ConsoleShuttleEvent.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Content.Server.Shuttles.Components;
|
||||
|
||||
namespace Content.Server.Shuttles.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a <see cref="ShuttleConsoleComponent"/> when it's trying to get its shuttle console to pilot.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public struct ConsoleShuttleEvent
|
||||
{
|
||||
public EntityUid? Console;
|
||||
}
|
||||
@@ -23,7 +23,16 @@ public sealed class RadarConsoleSystem : SharedRadarConsoleSystem
|
||||
|
||||
protected override void UpdateState(RadarConsoleComponent component)
|
||||
{
|
||||
var radarState = new RadarConsoleBoundInterfaceState(component.MaxRange, component.Owner, new List<DockingInterfaceState>());
|
||||
var xform = Transform(component.Owner);
|
||||
|
||||
var onGrid = xform.ParentUid == xform.GridUid;
|
||||
|
||||
var radarState = new RadarConsoleBoundInterfaceState(
|
||||
component.MaxRange,
|
||||
onGrid ? xform.Coordinates : null,
|
||||
onGrid ? xform.LocalRotation : null,
|
||||
new List<DockingInterfaceState>());
|
||||
|
||||
_uiSystem.GetUiOrNull(component.Owner, RadarConsoleUiKey.Key)?.SetState(radarState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Shuttles.Components;
|
||||
@@ -5,6 +6,7 @@ using Content.Server.Shuttles.Events;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Light;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
@@ -136,15 +138,29 @@ namespace Content.Server.Shuttles.Systems
|
||||
/// </summary>
|
||||
private void OnModeRequest(EntityUid uid, ShuttleConsoleComponent component, ShuttleModeRequestMessage args)
|
||||
{
|
||||
var consoleUid = uid;
|
||||
|
||||
if (TryComp<CargoPilotConsoleComponent>(uid, out var cargoPilot) && cargoPilot.Entity != null)
|
||||
{
|
||||
consoleUid = cargoPilot.Entity.Value;
|
||||
}
|
||||
|
||||
if (args.Session.AttachedEntity is not { } player ||
|
||||
!TryComp<PilotComponent>(player, out var pilot) ||
|
||||
!TryComp<TransformComponent>(player, out var xform) ||
|
||||
pilot.Console is not ShuttleConsoleComponent console) return;
|
||||
!TryComp<ShuttleConsoleComponent>(consoleUid, out var console) ||
|
||||
!TryComp<TransformComponent>(consoleUid, out var consoleXform)) return;
|
||||
|
||||
if (!console.SubscribedPilots.Contains(pilot) ||
|
||||
!TryComp<ShuttleComponent>(xform.GridUid, out var shuttle)) return;
|
||||
// Can't check console pilots as it may be remotely piloted!
|
||||
if (!component.SubscribedPilots.Contains(pilot) ||
|
||||
!TryComp<ShuttleComponent>(consoleXform.GridUid, out var shuttle)) return;
|
||||
|
||||
SetShuttleMode(args.Mode, console, shuttle);
|
||||
UpdateState(component);
|
||||
|
||||
if (uid != consoleUid)
|
||||
{
|
||||
UpdateState(console);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -159,7 +175,6 @@ namespace Content.Server.Shuttles.Systems
|
||||
!consoleXform.Anchored ||
|
||||
consoleXform.GridID != Transform(shuttleComponent.Owner).GridID)
|
||||
{
|
||||
UpdateState(consoleComponent);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -174,8 +189,6 @@ namespace Content.Server.Shuttles.Systems
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
UpdateState(consoleComponent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -205,10 +218,21 @@ namespace Content.Server.Shuttles.Systems
|
||||
|
||||
private void UpdateState(ShuttleConsoleComponent component, List<DockingInterfaceState>? docks = null)
|
||||
{
|
||||
TryComp<RadarConsoleComponent>(component.Owner, out var radar);
|
||||
EntityUid? entity = component.Owner;
|
||||
|
||||
var getShuttleEv = new ConsoleShuttleEvent
|
||||
{
|
||||
Console = entity,
|
||||
};
|
||||
|
||||
RaiseLocalEvent(entity.Value, ref getShuttleEv, false);
|
||||
entity = getShuttleEv.Console;
|
||||
|
||||
TryComp<TransformComponent>(entity, out var consoleXform);
|
||||
TryComp<RadarConsoleComponent>(entity, out var radar);
|
||||
var range = radar?.MaxRange ?? 0f;
|
||||
|
||||
TryComp<ShuttleComponent>(Transform(component.Owner).GridUid, out var shuttle);
|
||||
TryComp<ShuttleComponent>(consoleXform?.GridUid, out var shuttle);
|
||||
var mode = shuttle?.Mode ?? ShuttleMode.Cruise;
|
||||
|
||||
docks ??= GetAllDocks();
|
||||
@@ -217,7 +241,8 @@ namespace Content.Server.Shuttles.Systems
|
||||
?.SetState(new ShuttleConsoleBoundInterfaceState(
|
||||
mode,
|
||||
range,
|
||||
component.Owner,
|
||||
consoleXform?.Coordinates,
|
||||
consoleXform?.LocalRotation,
|
||||
docks));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user