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. ![image](https://github.com/tgstation/tgstation/assets/41715314/b8c7b00a-947e-421b-aa18-2b77f0b9bb50) ## 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. +

+ All new purchases will include the cost of the shipped crate, + which may be recycled afterwards. +
+
- + Current Credit Balance: {formatMoney(creditBalance)} cr. - + Current Order Cost: {formatMoney(orderBalance)} cr. + 150 ? 'green' : '#ad7526'} + > + {Math.round(data.updateTime / 10)} seconds until next + update +
- {materials.map((material, i) => ( -
- - - - - {toTitleCase(material.name)} - - - - Trading at {formatMoney(material.price)} cr. - - {material.price < material.threshold ? ( - - Material price critical! -
Trading temporarily suspended. + {sortBy((tempmat: Material) => tempmat.rarity)(materials).map( + (material, i) => ( +
+ + + + + {toTitleCase(material.name)} - ) : ( - - {material.quantity || 'Zero'} sheets of{' '} - {material.name} trading.{' '} - {material.requested || 'Zero'} sheets ordered. - - )} - + Trading at {formatMoney(material.price)} cr. + + {material.price < material.threshold ? ( + + Material price critical! +
Trading temporarily suspended. +
+ ) : ( + + {material.quantity || 'Zero'} sheets of{' '} + {material.name} trading.{' '} + {material.requested || 'Zero'} sheets ordered. + + )} + + + {toTitleCase(material.name)} is trending{' '} + {material.trend}. + +
+
+ + - - - - - - {material.requested > 0 && ( - x {material.requested} - )} -
-
- ))} + Buy 1 + + + + + +
+ {material.requested > 0 && ( + x {material.requested} + )} +
+
+ ), + )} );