Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stocks trading #2103

Merged
merged 20 commits into from
Nov 6, 2024
Prev Previous commit
Next Next commit
buying and selling
MilonPL committed Nov 3, 2024
commit 315fd0e44ffb7519c26a1f574b5f25259e91a5ab
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@
using Content.Client.Administration.UI.CustomControls;
using Content.Shared.CartridgeLoader.Cartridges;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;

@@ -59,7 +58,6 @@ private sealed class CompanyEntry
private readonly Button _buyButton;
private readonly Label _sharesLabel;
private readonly LineEdit _amountEdit;
private readonly string _companyName;
private readonly PriceHistoryTable _priceHistory;

// Define colors
@@ -69,7 +67,7 @@ private sealed class CompanyEntry

public CompanyEntry(string companyName, Action<string, float>? onBuyPressed, Action<string, float>? onSellPressed)
{
_companyName = companyName;
var companyName1 = companyName;

Container = new BoxContainer
{
@@ -182,21 +180,31 @@ public CompanyEntry(string companyName, Action<string, float>? onBuyPressed, Act
// Add horizontal separator after the panel
var separator = new HSeparator
{
Margin = new Thickness(5, 3, 5, 5)
Margin = new Thickness(5, 3, 5, 5),
};
Container.AddChild(separator);

// Button click events
_buyButton.OnPressed += _ =>
{
if (float.TryParse(_amountEdit.Text, out var amount))
onBuyPressed?.Invoke(_companyName, amount);
if (float.TryParse(_amountEdit.Text, out var amount) && amount > 0)
onBuyPressed?.Invoke(companyName1, amount);
};

_sellButton.OnPressed += _ =>
{
if (float.TryParse(_amountEdit.Text, out var amount))
onSellPressed?.Invoke(_companyName, amount);
if (float.TryParse(_amountEdit.Text, out var amount) && amount > 0)
onSellPressed?.Invoke(companyName1, amount);
};

// There has to be a better way of doing this
_amountEdit.OnTextChanged += args =>
{
var newText = string.Concat(args.Text.Where(char.IsDigit));
if (newText != args.Text)
{
_amountEdit.Text = newText;
}
};
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Numerics;
using Content.Shared.CartridgeLoader.Cartridges;
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Timing;

@@ -55,6 +56,12 @@ public sealed partial class StationStockMarketComponent : Component
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan NextUpdate = TimeSpan.Zero;

/// <summary>
/// The sound to play after selling or buying stocks
/// </summary>
[DataField]
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/Cargo/ping.ogg");

/// <summary>
/// The chance for minor market changes
/// </summary>
116 changes: 105 additions & 11 deletions Content.Server/DeltaV/Cargo/Systems/StockMarketSystem.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.Cargo.Components;
using Content.Server.Cargo.Systems;
using Content.Server.DeltaV.Cargo.Components;
using Content.Server.DeltaV.CartridgeLoader.Cartridges;
using Content.Shared.CartridgeLoader;
using Content.Shared.CartridgeLoader.Cartridges;
using Content.Shared.Database;
using Microsoft.CodeAnalysis.Elfie.Serialization;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Random;
using Robust.Shared.Timing;

@@ -17,13 +18,15 @@ namespace Content.Server.DeltaV.Cargo.Systems;
/// </summary>
public sealed class StockMarketSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = 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 IAdminLogManager _adminLogger = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;

private ISawmill _sawmill = default!;
public static readonly float MaxPrice = 262144; // 1/64 of max safe integer
private const float MaxPrice = 262144; // 1/64 of max safe integer

public override void Initialize()
{
@@ -55,31 +58,122 @@ private void OnStockTradingMessage(Entity<StockTradingCartridgeComponent> ent, r
return;

var name = message.Company;
var amount = message.Amount;
var comp = ent.Comp;
var station = comp.Station ?? default;
var amount = (int)message.Amount; // Convert to int since we can't have partial shares
var station = ent.Comp.Station;

if (station == null)
return;

// Check if the station has a stock market
if (!TryComp<StationStockMarketComponent>(station, out var stockMarket))
return;

// Validate company exists
if (!stockMarket.Companies.ContainsKey(name))
return;

// Log who's doing the transaction
// This will only display the PDA from which it was done, and not the user, but perhaps still somewhat useful for admins??
var loader = args.LoaderUid;

switch (message.Action)
{
case StockTradingUiAction.Buy:
_adminLogger.Add(LogType.Action,
LogImpact.Medium,
$"[StockMarket] Buying {amount} stocks of {name}");
$"{ToPrettyString(loader)} attempting to buy {amount} stocks of {name}");
BuyStocks(station.Value, stockMarket, name, amount);
break;
case StockTradingUiAction.Sell:
_adminLogger.Add(LogType.Action,
LogImpact.Medium,
$"[StockMarket] Selling {amount} stocks of {name}");
$"{ToPrettyString(loader)} attempting to sell {amount} stocks of {name}");
SellStocks(station.Value, stockMarket, name, amount);
break;
default:
throw new ArgumentOutOfRangeException();
}

// Update UI
var ev = new StockMarketUpdatedEvent(station);
var ev = new StockMarketUpdatedEvent(station.Value);
RaiseLocalEvent(ev);
}

private void BuyStocks(
EntityUid station,
StationStockMarketComponent stockMarket,
string companyName,
int amount)
{
if (amount <= 0)
return;

// Check if the station has a bank account
if (!TryComp<StationBankAccountComponent>(station, out var bank))
return;

// Check if the company exists
if (!stockMarket.Companies.TryGetValue(companyName, out var company))
return;

// Convert to int
var totalValue = (int)Math.Round(company.CurrentPrice * amount);

// Update stock ownership
if (!stockMarket.StockOwnership.TryGetValue(companyName, out var currentOwned))
currentOwned = 0;

// Update the bank account
_cargo.UpdateBankAccount(station, bank, -totalValue);

stockMarket.StockOwnership[companyName] = currentOwned + amount;

// Log the transaction
_adminLogger.Add(LogType.Action,
LogImpact.Medium,
$"[StockMarket] Bought {amount} stocks of {companyName} at {company.CurrentPrice:F2} credits each (Total: {totalValue})");
}

private void SellStocks(
EntityUid station,
StationStockMarketComponent stockMarket,
string companyName,
int amount)
{
if (amount <= 0)
return;

// Check if the station has a bank account
if (!TryComp<StationBankAccountComponent>(station, out var bank))
return;

// Check if the company exists
if (!stockMarket.Companies.TryGetValue(companyName, out var company))
return;

// Check if the station owns enough stocks
if (!stockMarket.StockOwnership.TryGetValue(companyName, out var currentOwned) || currentOwned < amount)
return;

// Convert to int
var totalValue = (int)Math.Round(company.CurrentPrice * amount);

// Update stock ownership
var newAmount = currentOwned - amount;
if (newAmount > 0)
stockMarket.StockOwnership[companyName] = newAmount;
else
stockMarket.StockOwnership.Remove(companyName);

// Update the bank account
_cargo.UpdateBankAccount(station, bank, totalValue);

// Log the transaction
_adminLogger.Add(LogType.Action,
LogImpact.Medium,
$"[StockMarket] Sold {amount} stocks of {companyName} at {company.CurrentPrice:F2} credits each (Total: {totalValue})");
}

private void UpdateStockPrices(EntityUid station, StationStockMarketComponent stockMarket)
{
var companies = stockMarket.Companies;
@@ -131,7 +225,7 @@ public bool TryChangeStocksPrice(EntityUid station,
// Check if it exceeds the max price
if (newPrice > MaxPrice)
{
_sawmill.Error($"New price cannot be greater than {MaxPrice}");
_sawmill.Error($"New price cannot be greater than {MaxPrice}.");
return false;
}

Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ namespace Content.Server.DeltaV.CartridgeLoader.Cartridges;
public sealed partial class StockTradingCartridgeComponent : Component
{
/// <summary>
/// Station entity keeping track of
/// Station entity to keep track of
/// </summary>
[DataField]
public EntityUid? Station;