From 3a76cb27fe114f51dd84242c729f3fd35cf94dec Mon Sep 17 00:00:00 2001
From: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com>
Date: Wed, 6 Mar 2024 18:54:37 -0500
Subject: [PATCH] Arcargo: Balance Pass v2 on the Stock Market (#81580)
## About The Pull Request
Picking up where I left off on #81216.
* Stock market stocks have had their market quantity drastically
reduced, as on both the more common and more rare material sides of
things these materials traditionally have been traded in quantities that
it would prevent bicycle like quantities of materials entering the game
while still allowing for access to rare materials at rare material rates
of credits.
* The stock market subsystem now fires once every 60 seconds, as opposed
to once every 20 seconds. To compensate for this, the subsystem now
makes much wider price changes every update, to disincentivize players
from just camping out at the stock market console all game, as this is
behavior we typically discourage (genetics/virology/etc). Material
tending times are similarly decreased to make up for that, while noting
that stock market events will still enable for a material to change
directions at any point as well.
* Material prices can drop below their minimum trading threshold,
resulting in them gaining protected purchasing status, and resulting in
them being unavailable for purchase. This means if you're watching a
price drop, and it's still trending lower, there's a distinct chance you
might want to buy before it drops below the threshold, or risk it, buy
later, and avoid the material getting locked out for another minute or
more.
* Adds 2 new stock market events to help add additional variety to the
stock market's variability, while adjusting the probability of a market
event occurring per stock market event. This should average to ~4 events
every minute, keeping things somewhat interesting if you're watching the
prices of items, but without requiring second to second updates to keep
things engaging.
* These two events include one that blocks off all material quantity
from a material for the duration of the event and resets prices when
complete, and another one that maximizes the profitability of a
material, but leaves it's market quantity up in the air.
* Stock blocks have had their freeze timer decreased from 5 minutes,
down to 3, with the warning now at 1.5 minutes. This is to encourage
players not to sit on their resources for longer periods of time if
their goal is just to sell at a specific price point and to keep items
going through the shuttle, which _also_ encourages players to receive
mail/receive regular orders from the rest of the crew.
* The UI has a number of improvements, those being:
* The time until the next stock market update is listed on the UI as an
active timer.
* The materials listed in the UI are now sorted by the value of that
resource per unit.
* The instructions are now kept within a collapsible component to cut
down on wasted space within the UI.
* A few elements are moved over to % width as opposed to a hardset x
pixels width for screen size compatibility purposes.

## Why It's Good For The Game
Stock market has been known to create bike levels of wealth with near
negligible amounts of effort and was going to need a balance pass
eventually. This is being accomplished by slowing down the system, but
also making it more unpredictable by expanding on the stock market event
system a bit further. Naturally, it could use a few more wacky events to
keep the system fresh and active, but for now this helps to keep the
system from being a screen simulator while also making game-health
changes like lowering material quantities that were capable of allowing
the player to double, quadruple, octuple, etc. their wealth every few
minutes by just buying low and selling high.
Makes a few QOL changes to the UI to compensate for a few of these
changes, like the new update timer on the UI in the case we change the
time per update any further, as well as to give incentive to players to
not just camp the console for new updates, just to glance at how their
investments are doing.
These tweaks also keep cargo moving as opposed to just trying to power
game iron and glass for maximum returns, while giving them extra
opportunities to send the shuttle to keep packages flowing for other
purchases/getting mail.
This has a chance to stop #79978, but I'll edit this appropriately after
a TM has confirmed if it was effective or not.
## Changelog
:cl:
balance: The stock market now fires slower, has stock market events
occur more often, and the stock market has fewer minerals that are
available to buy in a single purchase before restocking.
balance: Materials sold on the stock market may be protected from being
bought if their prices drop too low, so make sure you watch your prices
before they run the risk of getting shut out!
balance: Stock blocks now freeze the price of materials for 3 minutes,
down from 5.
qol: Tweaks to the Galactic Material Market UI, with materials sorted
based on their rarity and a timer to show how long until it updates.
add: New Stock market events, one locks a material from being purchased,
the other maximizes the value and quantity of a material for sale.
/:cl:
---------
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
---
code/__DEFINES/construction/material.dm | 8 +-
code/__DEFINES/economy.dm | 2 +-
code/controllers/subsystem/stock_market.dm | 16 +-
code/datums/stock_market_events.dm | 44 ++-
code/modules/cargo/materials_market.dm | 9 +-
tgui/packages/tgui/interfaces/MatMarket.tsx | 327 +++++++++++---------
6 files changed, 234 insertions(+), 172 deletions(-)
diff --git a/code/__DEFINES/construction/material.dm b/code/__DEFINES/construction/material.dm
index 24ab2eb3303..55c6d496e0c 100644
--- a/code/__DEFINES/construction/material.dm
+++ b/code/__DEFINES/construction/material.dm
@@ -68,13 +68,13 @@
//Stock market stock values.
/// How much quantity of a material stock exists for common materials like iron & glass.
-#define MATERIAL_QUANTITY_COMMON 25000
+#define MATERIAL_QUANTITY_COMMON 5000
/// How much quantity of a material stock exists for uncommon materials like silver & titanium.
-#define MATERIAL_QUANTITY_UNCOMMON 10000
+#define MATERIAL_QUANTITY_UNCOMMON 1000
/// How much quantity of a material stock exists for rare materials like gold, uranium, & diamond.
-#define MATERIAL_QUANTITY_RARE 2500
+#define MATERIAL_QUANTITY_RARE 200
/// How much quantity of a material stock exists for exotic materials like diamond & bluespace crystals.
-#define MATERIAL_QUANTITY_EXOTIC 500
+#define MATERIAL_QUANTITY_EXOTIC 50
// The number of ore vents that will spawn boulders with this material.
/// Is this material going to spawn often in ore vents? (80% of vents on lavaland)
diff --git a/code/__DEFINES/economy.dm b/code/__DEFINES/economy.dm
index 93b0678581a..7b619b274f5 100644
--- a/code/__DEFINES/economy.dm
+++ b/code/__DEFINES/economy.dm
@@ -75,7 +75,7 @@
#define MARKET_TREND_DOWNWARD -1
#define MARKET_TREND_STABLE 0
-#define MARKET_EVENT_PROBABILITY 1 //Probability of a market event firing, in percent. Fires once per material, every 20 seconds.
+#define MARKET_EVENT_PROBABILITY 8 //Probability of a market event firing, in percent. Fires once per material, every stock market tick.
#define MARKET_PROFIT_MODIFIER 0.8 //We don't make every sale a 1-1 of the actual buy price value, like with real life taxes and to encourage more smart trades
diff --git a/code/controllers/subsystem/stock_market.dm b/code/controllers/subsystem/stock_market.dm
index c9f632c7faf..6c4341adc8d 100644
--- a/code/controllers/subsystem/stock_market.dm
+++ b/code/controllers/subsystem/stock_market.dm
@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(stock_market)
name = "Stock Market"
- wait = 20 SECONDS
+ wait = 60 SECONDS
init_order = INIT_ORDER_DEFAULT
runlevels = RUNLEVEL_GAME
@@ -28,7 +28,7 @@ SUBSYSTEM_DEF(stock_market)
materials_trends[possible_market] = rand(MARKET_TREND_DOWNWARD,MARKET_TREND_UPWARD) //aka -1 to 1
materials_trend_life += possible_market
- materials_trend_life[possible_market] = rand(1,10)
+ materials_trend_life[possible_market] = rand(1,3)
materials_quantity += possible_market
materials_quantity[possible_market] = possible_market.tradable_base_quantity + (rand(-(possible_market.tradable_base_quantity) * 0.5, possible_market.tradable_base_quantity * 0.5))
@@ -80,7 +80,7 @@ SUBSYSTEM_DEF(stock_market)
materials_trends[mat] = MARKET_TREND_DOWNWARD
else
materials_trends[mat] = MARKET_TREND_STABLE
- materials_trend_life[mat] = rand(3,10) // Change our trend life for x number of fires of the subsystem
+ materials_trend_life[mat] = rand(1,3) // Change our trend life for x number of fires of the subsystem
else
materials_trend_life[mat] -= 1
@@ -88,14 +88,14 @@ SUBSYSTEM_DEF(stock_market)
var/quantity_change = 0
switch(trend)
if(MARKET_TREND_UPWARD)
- price_change = ROUND_UP(gaussian(price_units * 0.1, price_baseline * 0.05)) //If we don't ceil, small numbers will get trapped at low values
- quantity_change = -round(gaussian(quantity_baseline * 0.05, quantity_baseline * 0.05))
+ price_change = ROUND_UP(gaussian(price_units * 0.30, price_baseline * 0.15)) //If we don't ceil, small numbers will get trapped at low values
+ quantity_change = -round(gaussian(quantity_baseline * 0.15, quantity_baseline * 0.15))
if(MARKET_TREND_STABLE)
price_change = round(gaussian(0, price_baseline * 0.01))
- quantity_change = round(gaussian(0, quantity_baseline * 0.01))
+ quantity_change = round(gaussian(0, quantity_baseline * 0.5))
if(MARKET_TREND_DOWNWARD)
- price_change = -ROUND_UP(gaussian(price_units * 0.1, price_baseline * 0.05))
- quantity_change = round(gaussian(quantity_baseline * 0.05, quantity_baseline * 0.05))
+ price_change = -ROUND_UP(gaussian(price_units * 0.3, price_baseline * 0.15))
+ quantity_change = round(gaussian(quantity_baseline * 0.15, quantity_baseline * 0.15))
materials_prices[mat] = round(clamp(price_units + price_change, price_minimum, price_maximum))
materials_quantity[mat] = round(clamp(stock_quantity + quantity_change, 0, quantity_baseline * 2))
diff --git a/code/datums/stock_market_events.dm b/code/datums/stock_market_events.dm
index 81142d23002..4907bf784f6 100644
--- a/code/datums/stock_market_events.dm
+++ b/code/datums/stock_market_events.dm
@@ -83,7 +83,7 @@
/datum/stock_market_event/large_boost
name = "Large Boost!"
trend_value = MARKET_TREND_UPWARD
- trend_duration = 3
+ trend_duration = 4
circumstance = list(
"has just released a new product that raised the price of ",
"discovered a new valuable use for ",
@@ -93,14 +93,14 @@
/datum/stock_market_event/large_boost/start_event()
. = ..()
var/price_units = SSstock_market.materials_prices[mat]
- SSstock_market.materials_prices[mat] += round(gaussian(price_units * 0.5, price_units * 0.1))
+ SSstock_market.materials_prices[mat] += round(gaussian(price_units, price_units * 0.15))
SSstock_market.materials_prices[mat] = clamp(SSstock_market.materials_prices[mat], price_minimum * mat.value_per_unit, price_maximum * mat.value_per_unit)
create_news()
/datum/stock_market_event/large_drop
name = "Large Drop!"
trend_value = MARKET_TREND_DOWNWARD
- trend_duration = 5
+ trend_duration = 4
circumstance = list(
"'s latest product has seen major controversy, and resulted in a price drop for ",
"has been hit with a major lawsuit, resulting in a price drop for ",
@@ -110,6 +110,42 @@
/datum/stock_market_event/large_drop/start_event()
. = ..()
var/price_units = SSstock_market.materials_prices[mat]
- SSstock_market.materials_prices[mat] -= round(gaussian(price_units * 1.5, price_units * 0.1))
+ SSstock_market.materials_prices[mat] -= round(gaussian(price_units * 1.5, price_units * 0.15))
SSstock_market.materials_prices[mat] = clamp(SSstock_market.materials_prices[mat], price_minimum * mat.value_per_unit, price_maximum * mat.value_per_unit)
create_news()
+
+/datum/stock_market_event/hotcakes
+ name = "Selling like Hotcakes!"
+ trend_value = MARKET_TREND_UPWARD
+ trend_duration = 1
+ circumstance = list(
+ "has just released a new product that is dominating the market for ",
+ "is hitting it big! Dramatically stocking and raising the price of ",
+ ", in a surprise move, monopolized supply and has raised the price of ",
+ )
+
+/datum/stock_market_event/hotcakes/start_event()
+ . = ..()
+ SSstock_market.materials_prices[mat] = round(price_maximum * mat.value_per_unit)
+ create_news()
+
+/datum/stock_market_event/lockdown
+ name = "Lockdown!"
+ trend_value = MARKET_TREND_DOWNWARD
+ trend_duration = 2
+ circumstance = list(
+ "is being investigated by the Galactic Trade Commission, resulting in a halt of trade for ",
+ ", in a stunning move, has been embargoed by TerraGov, resulting in a halt of trade of ",
+ )
+
+/datum/stock_market_event/lockdown/handle()
+ . = ..()
+ SSstock_market.materials_quantity[mat] = 0 //Force the material to be unavailable.
+
+/datum/stock_market_event/lockdown/end_event()
+ . = ..()
+ SSstock_market.materials_quantity[mat] = initial(mat.tradable_base_quantity) //Force the material to be available again.
+ SSstock_market.materials_prices[mat] = initial(mat.value_per_unit) * SHEET_MATERIAL_AMOUNT //Force the price to be reset once the lockdown is over.
+ create_news()
+
+
diff --git a/code/modules/cargo/materials_market.dm b/code/modules/cargo/materials_market.dm
index 92d83d5d0a1..947197d16f2 100644
--- a/code/modules/cargo/materials_market.dm
+++ b/code/modules/cargo/materials_market.dm
@@ -166,12 +166,14 @@
var/min_value_override = initial(traded_mat.minimum_value_override)
if(min_value_override)
minimum_value_threshold = min_value_override
-
+ else
+ minimum_value_threshold = round(initial(traded_mat.value_per_unit) * SHEET_MATERIAL_AMOUNT * 0.5)
//send data
material_data += list(list(
"name" = initial(traded_mat.name),
"price" = SSstock_market.materials_prices[traded_mat],
+ "rarity" = initial(traded_mat.value_per_unit),
"threshold" = minimum_value_threshold,
"quantity" = SSstock_market.materials_quantity[traded_mat],
"trend" = trend_string,
@@ -205,6 +207,7 @@
.["orderBalance"] = current_cost
.["orderingPrive"] = ordering_private
.["canOrderCargo"] = can_buy_via_budget
+ .["updateTime"] = SSstock_market.next_fire - world.time
/obj/machinery/materials_market/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
@@ -348,8 +351,8 @@
/obj/item/stock_block/Initialize(mapload)
. = ..()
- addtimer(CALLBACK(src, PROC_REF(value_warning)), 2.5 MINUTES, TIMER_DELETE_ME)
- addtimer(CALLBACK(src, PROC_REF(update_value)), 5 MINUTES, TIMER_DELETE_ME)
+ addtimer(CALLBACK(src, PROC_REF(value_warning)), 1.5 MINUTES, TIMER_DELETE_ME)
+ addtimer(CALLBACK(src, PROC_REF(update_value)), 3 MINUTES, TIMER_DELETE_ME)
/obj/item/stock_block/examine(mob/user)
. = ..()
diff --git a/tgui/packages/tgui/interfaces/MatMarket.tsx b/tgui/packages/tgui/interfaces/MatMarket.tsx
index 41b25dedb00..1f6d69c8d69 100644
--- a/tgui/packages/tgui/interfaces/MatMarket.tsx
+++ b/tgui/packages/tgui/interfaces/MatMarket.tsx
@@ -1,14 +1,23 @@
+import { sortBy } from 'common/collections';
import { BooleanLike } from 'common/react';
import { toTitleCase } from 'common/string';
import { useBackend } from '../backend';
-import { Button, Modal, Section, Stack } from '../components';
+import {
+ Button,
+ Collapsible,
+ Modal,
+ NoticeBox,
+ Section,
+ Stack,
+} from '../components';
import { formatMoney } from '../format';
import { Window } from '../layouts';
type Material = {
name: string;
quantity: number;
+ rarity: number;
trend: string;
price: number;
threshold: number;
@@ -24,6 +33,7 @@ type Data = {
materials: Material[];
catastrophe: BooleanLike;
CARGO_CRATE_VALUE: number;
+ updateTime: number;
};
export const MatMarket = (props) => {
@@ -66,24 +76,35 @@ export const MatMarket = (props) => {
)
}
>
- Buy orders for material sheets placed here will be ordered on the next
- cargo shipment.
-
- To sell materials, please insert sheets or similar stacks of
- materials. All minerals sold on the market directly are subject to an
- 20% market fee. To prevent market manipulation, all registered traders
- can buy a total of 10 full stacks of materials at a time.
-
- All new purchases will include the cost of the shipped crate,
- which may be recycled afterwards.
+
+
+ Buy orders for material sheets placed here will be ordered on the
+ next cargo shipment.
+
+ To sell materials, please insert sheets or similar stacks of
+ materials. All minerals sold on the market directly are subject to
+ an 20% market fee. To prevent market manipulation, all registered
+ traders can buy a total of 10 full stacks of materials at a time.
+