diff --git a/Content.Client/_DV/CartridgeLoader/Cartridges/StockTradingUi.cs b/Content.Client/_DV/CartridgeLoader/Cartridges/StockTradingUi.cs index 15fa6900aeb..a182311a703 100644 --- a/Content.Client/_DV/CartridgeLoader/Cartridges/StockTradingUi.cs +++ b/Content.Client/_DV/CartridgeLoader/Cartridges/StockTradingUi.cs @@ -36,7 +36,7 @@ public override void UpdateState(BoundUserInterfaceState state) } } - private static void SendStockTradingUiMessage(StockTradingUiAction action, int company, float amount, BoundUserInterface userInterface) + private static void SendStockTradingUiMessage(StockTradingUiAction action, int company, int amount, BoundUserInterface userInterface) { var newsMessage = new StockTradingUiMessageEvent(action, company, amount); var message = new CartridgeUiMessage(newsMessage); diff --git a/Content.Client/_DV/CartridgeLoader/Cartridges/StockTradingUiFragment.xaml.cs b/Content.Client/_DV/CartridgeLoader/Cartridges/StockTradingUiFragment.xaml.cs index 2a18c2bbe96..34f71058c2b 100644 --- a/Content.Client/_DV/CartridgeLoader/Cartridges/StockTradingUiFragment.xaml.cs +++ b/Content.Client/_DV/CartridgeLoader/Cartridges/StockTradingUiFragment.xaml.cs @@ -14,8 +14,8 @@ public sealed partial class StockTradingUiFragment : BoxContainer private readonly Dictionary _companyEntries = new(); // Event handlers for the parent UI - public event Action? OnBuyButtonPressed; - public event Action? OnSellButtonPressed; + public event Action? OnBuyButtonPressed; + public event Action? OnSellButtonPressed; // Define colors public static readonly Color PositiveColor = Color.FromHex("#00ff00"); // Green @@ -70,8 +70,8 @@ private sealed class CompanyEntry public CompanyEntry(int companyIndex, string displayName, - Action? onBuyPressed, - Action? onSellPressed) + Action? onBuyPressed, + Action? onSellPressed) { Container = new BoxContainer { @@ -216,13 +216,13 @@ public CompanyEntry(int companyIndex, // Button click events _buyButton.OnPressed += _ => { - if (float.TryParse(_amountEdit.Text, out var amount) && amount > 0) + if (int.TryParse(_amountEdit.Text, out var amount) && amount > 0) onBuyPressed?.Invoke(companyIndex, amount); }; _sellButton.OnPressed += _ => { - if (float.TryParse(_amountEdit.Text, out var amount) && amount > 0) + if (int.TryParse(_amountEdit.Text, out var amount) && amount > 0) onSellPressed?.Invoke(companyIndex, amount); }; @@ -235,7 +235,7 @@ public CompanyEntry(int companyIndex, }; } - public void Update(StockCompanyStruct company, int ownedStocks) + public void Update(StockCompany company, int ownedStocks) { _nameLabel.Text = company.LocalizedDisplayName; _priceLabel.Text = $"${company.CurrentPrice:F2}"; diff --git a/Content.Server/_DV/Cargo/Components/StationStockMarketComponent.cs b/Content.Server/_DV/Cargo/Components/StationStockMarketComponent.cs index b3b82cc4337..b3fd07585c1 100644 --- a/Content.Server/_DV/Cargo/Components/StationStockMarketComponent.cs +++ b/Content.Server/_DV/Cargo/Components/StationStockMarketComponent.cs @@ -16,7 +16,7 @@ public sealed partial class StationStockMarketComponent : Component /// The list of companies you can invest in /// [DataField] - public List Companies = []; + public List Companies = []; /// /// The list of shares owned by the station @@ -53,19 +53,12 @@ public sealed partial class StationStockMarketComponent : Component [DataField] public List MarketChanges = [ - new() { Chance = 0.86f, Range = new Vector2(-0.05f, 0.05f) }, // Minor - new() { Chance = 0.10f, Range = new Vector2(-0.3f, 0.2f) }, // Moderate - new() { Chance = 0.03f, Range = new Vector2(-0.5f, 1.5f) }, // Major - new() { Chance = 0.01f, Range = new Vector2(-0.9f, 4.0f) }, // Catastrophic + new(0.86f, new Vector2(-0.05f, 0.05f)), // Minor + new(0.10f, new Vector2(-0.3f, 0.2f)), // Moderate + new(0.03f, new Vector2(-0.5f, 1.5f)), // Major + new(0.01f, new Vector2(-0.9f, 4.0f)), // Catastrophic ]; } -[DataDefinition] -public sealed partial class MarketChange -{ - [DataField(required: true)] - public float Chance; - - [DataField(required: true)] - public Vector2 Range; -} +[DataRecord] +public record struct MarketChange(float Chance, Vector2 Range); diff --git a/Content.Server/_DV/Cargo/Systems/StockMarketSystem.cs b/Content.Server/_DV/Cargo/Systems/StockMarketSystem.cs index 2d490330c28..175743dde99 100644 --- a/Content.Server/_DV/Cargo/Systems/StockMarketSystem.cs +++ b/Content.Server/_DV/Cargo/Systems/StockMarketSystem.cs @@ -9,9 +9,7 @@ using Content.Shared.CartridgeLoader; using Content.Shared.CartridgeLoader.Cartridges; using Content.Shared.Database; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; -using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -22,15 +20,14 @@ namespace Content.Server._DV.Cargo.Systems; /// public sealed class StockMarketSystem : EntitySystem { - [Dependency] private readonly AccessReaderSystem _accessSystem = default!; + [Dependency] private readonly AccessReaderSystem _access = default!; [Dependency] private readonly CargoSystem _cargo = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly ILogManager _log = default!; [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IdCardSystem _idCardSystem = default!; + [Dependency] private readonly IdCardSystem _idCard = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; private ISawmill _sawmill = default!; private const float MaxPrice = 262144; // 1/64 of max safe integer @@ -64,38 +61,27 @@ private void OnStockTradingMessage(Entity ent, r if (args is not StockTradingUiMessageEvent message) return; + var user = args.Actor; var companyIndex = message.CompanyIndex; - var amount = (int)message.Amount; - var station = ent.Comp.Station; + var amount = message.Amount; var loader = GetEntity(args.LoaderUid); - var xform = Transform(loader); // Ensure station and stock market components are valid - if (station == null || !TryComp(station, out var stockMarket)) + if (ent.Comp.Station is not {} station || !TryComp(station, out var stockMarket)) return; // Validate company index if (companyIndex < 0 || companyIndex >= stockMarket.Companies.Count) return; - if (!TryComp(ent.Owner, out var access)) + if (!TryComp(ent, out var access)) return; - // Attempt to retrieve ID card from loader - IdCardComponent? idCard = null; - if (_idCardSystem.TryGetIdCard(loader, out var pdaId)) - idCard = pdaId; - - // Play deny sound and exit if access is not allowed - if (idCard == null || !_accessSystem.IsAllowed(pdaId.Owner, ent.Owner, access)) + // Attempt to retrieve ID card from loader, + // play deny sound and exit if access is not allowed + if (!_idCard.TryGetIdCard(loader, out var idCard) || !_access.IsAllowed(idCard, ent.Owner, access)) { - _audio.PlayEntity( - stockMarket.DenySound, - Filter.Empty().AddInRange(_transform.GetMapCoordinates(loader, xform), 0.05f), - loader, - true, - AudioParams.Default.WithMaxDistance(0.05f) - ); + _audio.PlayEntity(stockMarket.DenySound, loader, user); return; } @@ -110,15 +96,15 @@ private void OnStockTradingMessage(Entity ent, r case StockTradingUiAction.Buy: _adminLogger.Add(LogType.Action, LogImpact.Medium, - $"{ToPrettyString(loader)} attempting to buy {amount} stocks of {company.LocalizedDisplayName}"); - success = TryBuyStocks(station.Value, stockMarket, companyIndex, amount); + $"{ToPrettyString(user):user} attempting to buy {amount} stocks of {company.LocalizedDisplayName}"); + success = TryChangeStocks(station, stockMarket, companyIndex, amount, user); break; case StockTradingUiAction.Sell: _adminLogger.Add(LogType.Action, LogImpact.Medium, - $"{ToPrettyString(loader)} attempting to sell {amount} stocks of {company.LocalizedDisplayName}"); - success = TrySellStocks(station.Value, stockMarket, companyIndex, amount); + $"{ToPrettyString(user):user} attempting to sell {amount} stocks of {company.LocalizedDisplayName}"); + success = TryChangeStocks(station, stockMarket, companyIndex, -amount, user); break; default: @@ -126,32 +112,29 @@ private void OnStockTradingMessage(Entity ent, r } // Play confirmation sound if the transaction was successful - if (success) - { - _audio.PlayEntity( - stockMarket.ConfirmSound, - Filter.Empty().AddInRange(_transform.GetMapCoordinates(loader, xform), 0.05f), - loader, - true, - AudioParams.Default.WithMaxDistance(0.05f) - ); - } + _audio.PlayEntity(success ? stockMarket.ConfirmSound : stockMarket.DenySound, loader, user); } finally { // Raise the event to update the UI regardless of outcome - var ev = new StockMarketUpdatedEvent(station.Value); - RaiseLocalEvent(ev); + UpdateStockMarket(station); } } - private bool TryBuyStocks( + private void UpdateStockMarket(EntityUid station) + { + var ev = new StockMarketUpdatedEvent(station); + RaiseLocalEvent(ref ev); + } + + private bool TryChangeStocks( EntityUid station, StationStockMarketComponent stockMarket, int companyIndex, - int amount) + int amount, + EntityUid user) { - if (amount <= 0 || companyIndex < 0 || companyIndex >= stockMarket.Companies.Count) + if (amount == 0 || companyIndex < 0 || companyIndex >= stockMarket.Companies.Count) return false; // Check if the station has a bank account @@ -161,58 +144,37 @@ private bool TryBuyStocks( var company = stockMarket.Companies[companyIndex]; var totalValue = (int)Math.Round(company.CurrentPrice * amount); - // See if we can afford it - if (bank.Balance < totalValue) - return false; - if (!stockMarket.StockOwnership.TryGetValue(companyIndex, out var currentOwned)) currentOwned = 0; - // Update the bank account - _cargo.UpdateBankAccount(station, bank, -totalValue); - stockMarket.StockOwnership[companyIndex] = currentOwned + amount; - - // Log the transaction - _adminLogger.Add(LogType.Action, - LogImpact.Medium, - $"[StockMarket] Bought {amount} stocks of {company.LocalizedDisplayName} at {company.CurrentPrice:F2} credits each (Total: {totalValue})"); - - return true; - } - - private bool TrySellStocks( - EntityUid station, - StationStockMarketComponent stockMarket, - int companyIndex, - int amount) - { - if (amount <= 0 || companyIndex < 0 || companyIndex >= stockMarket.Companies.Count) - return false; - - // Check if the station has a bank account - if (!TryComp(station, out var bank)) - return false; - - if (!stockMarket.StockOwnership.TryGetValue(companyIndex, out var currentOwned) || currentOwned < amount) - return false; - - var company = stockMarket.Companies[companyIndex]; - var totalValue = (int)Math.Round(company.CurrentPrice * amount); + if (amount > 0) + { + // Buying: see if we can afford it + if (bank.Balance < totalValue) + return false; + } + else + { + // Selling: see if we have enough stocks to sell + var selling = -amount; + if (currentOwned < selling) + return false; + } - // Update stock ownership - var newAmount = currentOwned - amount; + var newAmount = currentOwned + amount; if (newAmount > 0) stockMarket.StockOwnership[companyIndex] = newAmount; else stockMarket.StockOwnership.Remove(companyIndex); - // Update the bank account - _cargo.UpdateBankAccount(station, bank, totalValue); + // Update the bank account (take away for buying and give for selling) + _cargo.UpdateBankAccount(station, bank, -totalValue); // Log the transaction + var verb = amount > 0 ? "bought" : "sold"; _adminLogger.Add(LogType.Action, LogImpact.Medium, - $"[StockMarket] Sold {amount} stocks of {company.LocalizedDisplayName} at {company.CurrentPrice:F2} credits each (Total: {totalValue})"); + $"[StockMarket] {ToPrettyString(user):user} {verb} {Math.Abs(amount)} stocks of {company.LocalizedDisplayName} at {company.CurrentPrice:F2} credits each (Total: {totalValue})"); return true; } @@ -225,7 +187,7 @@ private void UpdateStockPrices(EntityUid station, StationStockMarketComponent st var changeType = DetermineMarketChange(stockMarket.MarketChanges); var multiplier = CalculatePriceMultiplier(changeType); - UpdatePriceHistory(company); + UpdatePriceHistory(ref company); // Update price with multiplier var oldPrice = company.CurrentPrice; @@ -243,8 +205,7 @@ private void UpdateStockPrices(EntityUid station, StationStockMarketComponent st var percentChange = (company.CurrentPrice - oldPrice) / oldPrice * 100; // Raise the event - var ev = new StockMarketUpdatedEvent(station); - RaiseLocalEvent(ev); + UpdateStockMarket(station); // Log it _adminLogger.Add(LogType.Action, @@ -273,13 +234,12 @@ public bool TryChangeStocksPrice(EntityUid station, return false; var company = stockMarket.Companies[companyIndex]; - UpdatePriceHistory(company); + UpdatePriceHistory(ref company); company.CurrentPrice = MathF.Max(newPrice, company.BasePrice * 0.1f); stockMarket.Companies[companyIndex] = company; - var ev = new StockMarketUpdatedEvent(station); - RaiseLocalEvent(ev); + UpdateStockMarket(station); return true; } @@ -293,7 +253,7 @@ public bool TryAddCompany(EntityUid station, string displayName) { // Create a new company struct with the specified parameters - var company = new StockCompanyStruct + var company = new StockCompany { LocalizedDisplayName = displayName, // Assume there's no Loc for it BasePrice = basePrice, @@ -301,36 +261,33 @@ public bool TryAddCompany(EntityUid station, PriceHistory = [], }; + UpdatePriceHistory(ref company); stockMarket.Companies.Add(company); - UpdatePriceHistory(company); - var ev = new StockMarketUpdatedEvent(station); - RaiseLocalEvent(ev); + UpdateStockMarket(station); return true; } /// - /// Attempts to add a new company to the station using the StockCompanyStruct + /// Attempts to add a new company to the station using the StockCompany /// /// False if the company already exists, true otherwise - public bool TryAddCompany(EntityUid station, - StationStockMarketComponent stockMarket, - StockCompanyStruct company) + public bool TryAddCompany(Entity station, + StockCompany company) { - // Add the new company to the dictionary - stockMarket.Companies.Add(company); - // Make sure it has a price history - UpdatePriceHistory(company); + UpdatePriceHistory(ref company); - var ev = new StockMarketUpdatedEvent(station); - RaiseLocalEvent(ev); + // Add the new company to the dictionary + station.Comp.Companies.Add(company); + + UpdateStockMarket(station); return true; } - private static void UpdatePriceHistory(StockCompanyStruct company) + private static void UpdatePriceHistory(ref StockCompany company) { // Create if null company.PriceHistory ??= []; @@ -379,7 +336,9 @@ private float CalculatePriceMultiplier(MarketChange change) return Math.Clamp(result, change.Range.X, change.Range.Y); } } -public sealed class StockMarketUpdatedEvent(EntityUid station) : EntityEventArgs -{ - public EntityUid Station = station; -} + +/// +/// Broadcast whenever a stock market is updated. +/// +[ByRefEvent] +public record struct StockMarketUpdatedEvent(EntityUid Station); diff --git a/Content.Server/_DV/CartridgeLoader/Cartridges/StockTradingCartridgeSystem.cs b/Content.Server/_DV/CartridgeLoader/Cartridges/StockTradingCartridgeSystem.cs index c2d114c7164..e8677ea01b7 100644 --- a/Content.Server/_DV/CartridgeLoader/Cartridges/StockTradingCartridgeSystem.cs +++ b/Content.Server/_DV/CartridgeLoader/Cartridges/StockTradingCartridgeSystem.cs @@ -27,7 +27,7 @@ public override void Initialize() private void OnBalanceUpdated(Entity ent, ref BankBalanceUpdatedEvent args) { - UpdateAllCartridges(args.Station); + UpdateAllCartridges(args.Station); } private void OnUiReady(Entity ent, ref CartridgeUiReadyEvent args) @@ -35,7 +35,7 @@ private void OnUiReady(Entity ent, ref Cartridge UpdateUI(ent, args.Loader); } - private void OnStockMarketUpdated(StockMarketUpdatedEvent args) + private void OnStockMarketUpdated(ref StockMarketUpdatedEvent args) { UpdateAllCartridges(args.Station); } @@ -81,17 +81,9 @@ private void UpdateUI(Entity ent, EntityUid load !TryComp(ent.Comp.Station, out var bankAccount)) return; - // Convert company data to UI state format - var entries = stockMarket.Companies.Select(company => new StockCompanyStruct( - displayName: company.LocalizedDisplayName, - currentPrice: company.CurrentPrice, - basePrice: company.BasePrice, - priceHistory: company.PriceHistory)) - .ToList(); - // Send the UI state with balance and owned stocks var state = new StockTradingUiState( - entries: entries, + entries: stockMarket.Companies, ownedStocks: stockMarket.StockOwnership, balance: bankAccount.Balance ); diff --git a/Content.Shared/_DV/CartridgeLoader/Cartridges/StockTradingUiMessageEvent.cs b/Content.Shared/_DV/CartridgeLoader/Cartridges/StockTradingUiMessageEvent.cs index a80f8c6b8a8..5981f03b28e 100644 --- a/Content.Shared/_DV/CartridgeLoader/Cartridges/StockTradingUiMessageEvent.cs +++ b/Content.Shared/_DV/CartridgeLoader/Cartridges/StockTradingUiMessageEvent.cs @@ -3,12 +3,12 @@ namespace Content.Shared.CartridgeLoader.Cartridges; [Serializable, NetSerializable] -public sealed class StockTradingUiMessageEvent(StockTradingUiAction action, int companyIndex, float amount) +public sealed class StockTradingUiMessageEvent(StockTradingUiAction action, int companyIndex, int amount) : CartridgeMessageEvent { public readonly StockTradingUiAction Action = action; public readonly int CompanyIndex = companyIndex; - public readonly float Amount = amount; + public readonly int Amount = amount; } [Serializable, NetSerializable] diff --git a/Content.Shared/_DV/CartridgeLoader/Cartridges/StockTradingUiState.cs b/Content.Shared/_DV/CartridgeLoader/Cartridges/StockTradingUiState.cs index aea4ba5aa1d..42adb6feaa8 100644 --- a/Content.Shared/_DV/CartridgeLoader/Cartridges/StockTradingUiState.cs +++ b/Content.Shared/_DV/CartridgeLoader/Cartridges/StockTradingUiState.cs @@ -4,19 +4,19 @@ namespace Content.Shared.CartridgeLoader.Cartridges; [Serializable, NetSerializable] public sealed class StockTradingUiState( - List entries, + List entries, Dictionary ownedStocks, float balance) : BoundUserInterfaceState { - public readonly List Entries = entries; + public readonly List Entries = entries; public readonly Dictionary OwnedStocks = ownedStocks; public readonly float Balance = balance; } // No structure, zero fucks given [DataDefinition, Serializable] -public partial struct StockCompanyStruct +public partial struct StockCompany { /// /// The displayed name of the company shown in the UI. @@ -55,7 +55,7 @@ public string LocalizedDisplayName [DataField] public List? PriceHistory; - public StockCompanyStruct(string displayName, float currentPrice, float basePrice, List? priceHistory) + public StockCompany(string displayName, float currentPrice, float basePrice, List? priceHistory) { DisplayName = displayName; _displayName = null;