diff --git a/code/__DEFINES/economy.dm b/code/__DEFINES/economy.dm
index 7e12f3904b6..9d6841b56fd 100644
--- a/code/__DEFINES/economy.dm
+++ b/code/__DEFINES/economy.dm
@@ -73,3 +73,30 @@
#define PAYMENT_CLINICAL "clinical"
#define PAYMENT_FRIENDLY "friendly"
#define PAYMENT_ANGRY "angry"
+
+#define MARKET_TREND_UPWARD 1
+#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_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
+
+/// Create quantity subtypes for stock market datums.
+#define MARKET_QUANTITY_HELPERS(path) ##path/one {\
+ amount = 1; \
+} \
+##path/five {\
+ amount = 5; \
+} \
+##path/ten {\
+ amount = 10; \
+} \
+##path/twenty_five {\
+ amount = 25; \
+} \
+##path/fifty {\
+ amount = 50; \
+}
+
+
diff --git a/code/__DEFINES/materials.dm b/code/__DEFINES/materials.dm
index 08b88324826..5fc5cc08ea2 100644
--- a/code/__DEFINES/materials.dm
+++ b/code/__DEFINES/materials.dm
@@ -75,3 +75,13 @@
#define MATERIAL_SLOWDOWN_PLASTEEL (0.05)
/// The slowdown value of one [SHEET_MATERIAL_AMOUNT] of alien alloy.
#define MATERIAL_SLOWDOWN_ALIEN_ALLOY (0.1)
+
+//Stock market stock values.
+/// How much quantity of a material stock exists for common materials like iron & glass.
+#define MATERIAL_QUANTITY_COMMON 25000
+/// How much quantity of a material stock exists for uncommon materials like silver & titanium.
+#define MATERIAL_QUANTITY_UNCOMMON 10000
+/// How much quantity of a material stock exists for rare materials like gold, uranium, & diamond.
+#define MATERIAL_QUANTITY_RARE 2500
+/// How much quantity of a material stock exists for exotic materials like diamond & bluespace crystals.
+#define MATERIAL_QUANTITY_EXOTIC 500
diff --git a/code/controllers/subsystem/economy.dm b/code/controllers/subsystem/economy.dm
index b560f12ee20..68fb43d7c2e 100644
--- a/code/controllers/subsystem/economy.dm
+++ b/code/controllers/subsystem/economy.dm
@@ -122,6 +122,8 @@ SUBSYSTEM_DEF(economy)
var/effective_mailcount = round(living_player_count()/(inflation_value - 0.5)) //More mail at low inflation, and vis versa.
mail_waiting += clamp(effective_mailcount, 1, MAX_MAIL_PER_MINUTE * seconds_per_tick)
+ SSstock_market.news_string = ""
+
/**
* Handy proc for obtaining a department's bank account, given the department ID, AKA the define assigned for what department they're under.
*/
@@ -172,7 +174,7 @@ SUBSYSTEM_DEF(economy)
fluff_string = ", but company countermeasures protect YOU from being affected!"
else
fluff_string = ", and company countermeasures are failing to protect YOU from being affected. We're all doomed!"
- earning_report = "Sector Economic Report
Sector vendor prices is currently at [SSeconomy.inflation_value()*100]%[fluff_string]
The station spending power is currently [station_total] Credits, and the crew's targeted allowance is at [station_target] Credits.
That's all from the Nanotrasen Economist Division."
+ earning_report = "Sector Economic Report
Sector vendor prices is currently at [SSeconomy.inflation_value()*100]%[fluff_string]
The station spending power is currently [station_total] Credits, and the crew's targeted allowance is at [station_target] Credits.
[SSstock_market.news_string] That's all from the Nanotrasen Economist Division."
GLOB.news_network.submit_article(earning_report, "Station Earnings Report", "Station Announcements", null, update_alert = FALSE)
return TRUE
diff --git a/code/controllers/subsystem/stock_market.dm b/code/controllers/subsystem/stock_market.dm
new file mode 100644
index 00000000000..7c2cb71dc49
--- /dev/null
+++ b/code/controllers/subsystem/stock_market.dm
@@ -0,0 +1,154 @@
+
+SUBSYSTEM_DEF(stock_market)
+ name = "Stock Market"
+ wait = 20 SECONDS
+ init_order = INIT_ORDER_DEFAULT
+ runlevels = RUNLEVEL_GAME
+
+ /// Associated list of materials and their prices at the given time.
+ var/list/materials_prices = list()
+ /// Associated list of materials alongside their market trends. 1 is up, 0 is stable, -1 is down.
+ var/list/materials_trends = list()
+ /// Associated list of materials alongside the life of it's current trend. After it's life is up, it will change to a new trend.
+ var/list/materials_trend_life = list()
+ /// Associated list of materials alongside their available quantity. This is used to determine how much of a material is available to buy, and how much buying and selling affects the price.
+ var/list/materials_quantity = list()
+ /// HTML string that is used to display the market events to the player.
+ var/news_string = ""
+
+/datum/controller/subsystem/stock_market/Initialize()
+ for(var/datum/material/possible_market as anything in subtypesof(/datum/material)) // I need to make this work like this, but lets hardcode it for now
+ if(initial(possible_market.tradable))
+ materials_prices += possible_market
+ materials_prices[possible_market] = initial(possible_market.value_per_unit) * SHEET_MATERIAL_AMOUNT
+
+ materials_trends += possible_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_quantity += possible_market
+ materials_quantity[possible_market] = initial(possible_market.tradable_base_quantity) + (rand(-initial(possible_market.tradable_base_quantity) * 0.5, initial(possible_market.tradable_base_quantity) * 0.5))
+ return SS_INIT_SUCCESS
+/datum/controller/subsystem/stock_market/fire(resumed)
+ for(var/datum/material/market as anything in materials_prices)
+ handle_trends_and_price(market)
+
+/**
+ * Handles shifts in the cost of materials, and in what direction the material is most likely to move.
+ */
+/datum/controller/subsystem/stock_market/proc/handle_trends_and_price(datum/material/mat)
+ if(prob(MARKET_EVENT_PROBABILITY))
+ handle_market_event(mat)
+ return
+ var/trend = materials_trends[mat]
+ var/trend_life = materials_trend_life[mat]
+
+ var/price_units = materials_prices[mat]
+ var/price_minimum = round(initial(mat.value_per_unit) * SHEET_MATERIAL_AMOUNT * 0.5)
+ if(!isnull(initial(mat.minimum_value_override)))
+ price_minimum = round(initial(mat.minimum_value_override) * SHEET_MATERIAL_AMOUNT)
+ var/price_maximum = round(initial(mat.value_per_unit) * SHEET_MATERIAL_AMOUNT * 3)
+ var/price_baseline = initial(mat.value_per_unit) * SHEET_MATERIAL_AMOUNT
+
+ var/stock_quantity = materials_quantity[mat]
+
+ if(HAS_TRAIT(SSeconomy, TRAIT_MARKET_CRASHING)) //We hardset to the worst possible price and lowest possible impact if sold
+ materials_prices[mat] = price_minimum
+ materials_quantity[mat] = stock_quantity * 2
+ materials_trends[mat] = MARKET_TREND_DOWNWARD
+ trend_life = materials_trend_life[mat] = 1
+ return
+
+ if(trend_life == 0)
+ ///We want to scale our trend so that if we're closer to our minimum or maximum price, we're more likely to trend the other way.
+ if((price_units < price_baseline))
+ var/chance_swap = 100 - ((clamp((price_units - price_minimum), 1, 1000) / (price_baseline - price_minimum))*100)
+ if(prob(chance_swap))
+ materials_trends[mat] = MARKET_TREND_UPWARD
+ else
+ materials_trends[mat] = MARKET_TREND_STABLE
+ else if((price_units > price_baseline))
+ var/chance_swap = 100 - ((clamp((price_units - price_maximum), 1, 1000) / (price_maximum - price_baseline))*100)
+ if(prob(chance_swap))
+ 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 cycles
+ else
+ materials_trend_life[mat] -= 1
+
+ var/price_change = 0
+ 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(stock_quantity * 0.1, stock_quantity * 0.05))
+ if(MARKET_TREND_STABLE)
+ price_change = round(gaussian(0, price_baseline * 0.01))
+ quantity_change = round(gaussian(0, stock_quantity * 0.01))
+ if(MARKET_TREND_DOWNWARD)
+ price_change = -ROUND_UP(gaussian(price_units * 0.1, price_baseline * 0.05))
+ quantity_change = round(gaussian(stock_quantity * 0.1, stock_quantity * 0.05))
+ materials_prices[mat] = round(clamp(price_units + price_change, price_minimum, price_maximum))
+ materials_quantity[mat] = round(clamp(stock_quantity + quantity_change, 0, initial(mat.tradable_base_quantity) * 2))
+
+/**
+ * Market events are a way to spice up the market and make it more interesting.
+ * Randomly one will occur to a random material, and it will change the price of that material more drastically, or reset it to a stable price.
+ * Events are also broadcast to the newscaster as a fun little fluff piece. Good way to tell some lore as well, or just make a joke.
+ */
+/datum/controller/subsystem/stock_market/proc/handle_market_event(datum/material/mat)
+
+ var/company_name = list( // Pick a random company name from the list, I let copilot make a few up for me which is why some suck
+ "Nakamura Engineering",
+ "Robust Industries, LLC",
+ "MODular Solutions",
+ "SolGov",
+ "Australicus Industrial Mining",
+ "Vey-Medical",
+ "Aussec Armory",
+ "Dreamland Robotics"
+ )
+ var/circumstance
+ var/event = rand(1,3)
+
+ var/price_units = materials_prices[mat]
+ var/price_minimum = round(initial(mat.value_per_unit) * SHEET_MATERIAL_AMOUNT * 0.5)
+ if(!isnull(initial(mat.minimum_value_override)))
+ price_minimum = round(initial(mat.minimum_value_override) * SHEET_MATERIAL_AMOUNT)
+ var/price_maximum = round(initial(mat.value_per_unit) * SHEET_MATERIAL_AMOUNT * 3)
+ var/price_baseline = initial(mat.value_per_unit) * SHEET_MATERIAL_AMOUNT
+
+ switch(event)
+ if(1) //Reset to stable
+ materials_prices[mat] = price_baseline
+ materials_trends[mat] = MARKET_TREND_STABLE
+ materials_trend_life[mat] = 1
+ circumstance = pick(list(
+ "[pick(company_name)] has been bought out by a private investment firm. As a result, [initial(mat.name)] is now stable at [materials_prices[mat]] cr.",
+ "Due to a corporate restructuring, the largest supplier of [initial(mat.name)] has had the price changed to [materials_prices[mat]] cr.",
+ "[initial(mat.name)] is now under a monopoly by [pick(company_name)]. The price has been changed to [materials_prices[mat]] cr accordingly."
+ ))
+ if(2) //Big boost
+ materials_prices[mat] += round(gaussian(price_units * 0.5, price_units * 0.1))
+ materials_prices[mat] = clamp(materials_prices[mat], price_minimum, price_maximum)
+ materials_trends[mat] = MARKET_TREND_UPWARD
+ materials_trend_life[mat] = rand(1,5)
+ circumstance = pick(list(
+ "[pick(company_name)] has just released a new product that uses [initial(mat.name)]! As a result, the price has been raised to [materials_prices[mat]] cr.",
+ "Due to [pick(company_name)] finding a new property of [initial(mat.name)], its price has been raised to [materials_prices[mat]] cr.",
+ "A study has found that [initial(mat.name)] may run out within the next 100 years. The price has raised to [materials_prices[mat]] cr due to panic."
+ ))
+ if(3) //Big drop
+ materials_prices[mat] -= round(gaussian(price_units * 1.5, price_units * 0.1))
+ materials_prices[mat] = clamp(materials_prices[mat], price_minimum, price_maximum)
+ materials_trends[mat] = MARKET_TREND_DOWNWARD
+ materials_trend_life[mat] = rand(1,5)
+ circumstance = pick(list(
+ "[pick(company_name)]'s latest product has seen major controversy, and as a result, the price of [initial(mat.name)] has dropped to [materials_prices[mat]] cr.",
+ "Due to a new competitor, the price of [initial(mat.name)] has dropped to [materials_prices[mat]] cr.",
+ "[initial(mat.name)] has been found to be a carcinogen. The price has dropped to [materials_prices[mat]] cr due to panic."
+ ))
+ news_string += circumstance + " " // Add the event to the news_string, formatted for newscasters.
diff --git a/code/datums/materials/_material.dm b/code/datums/materials/_material.dm
index d91884d972e..06d26f31ea3 100644
--- a/code/datums/materials/_material.dm
+++ b/code/datums/materials/_material.dm
@@ -33,8 +33,16 @@ Simple datum which is instanced once per type and is used for every object of sa
var/strength_modifier = 1
///This is a modifier for integrity, and resembles the strength of the material
var/integrity_modifier = 1
+
///This is the amount of value per 1 unit of the material
var/value_per_unit = 0
+ ///This is the minimum value of the material, used in the stock market for any mat that isn't set to null
+ var/minimum_value_override = null
+ ///Is this material traded on the stock market?
+ var/tradable = FALSE
+ ///If this material is tradable, what is the base quantity of the material on the stock market?
+ var/tradable_base_quantity = 0
+
///Armor modifiers, multiplies an items normal armor vars by these amounts.
var/armor_modifiers = list(MELEE = 1, BULLET = 1, LASER = 1, ENERGY = 1, BOMB = 1, BIO = 1, FIRE = 1, ACID = 1)
///How beautiful is this material per unit.
diff --git a/code/datums/materials/basemats.dm b/code/datums/materials/basemats.dm
index fcef1b5d3d8..f79b9f7e422 100644
--- a/code/datums/materials/basemats.dm
+++ b/code/datums/materials/basemats.dm
@@ -7,6 +7,9 @@
categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
sheet_type = /obj/item/stack/sheet/iron
value_per_unit = 5 / SHEET_MATERIAL_AMOUNT
+ minimum_value_override = 0
+ tradable = TRUE
+ tradable_base_quantity = MATERIAL_QUANTITY_COMMON
/datum/material/iron/on_accidental_mat_consumption(mob/living/carbon/victim, obj/item/source_item)
victim.apply_damage(10, BRUTE, BODY_ZONE_HEAD, wound_bonus = 5)
@@ -25,6 +28,9 @@
shard_type = /obj/item/shard
debris_type = /obj/effect/decal/cleanable/glass
value_per_unit = 5 / SHEET_MATERIAL_AMOUNT
+ minimum_value_override = 0
+ tradable = TRUE
+ tradable_base_quantity = MATERIAL_QUANTITY_COMMON
beauty_modifier = 0.05
armor_modifiers = list(MELEE = 0.2, BULLET = 0.2, ENERGY = 1, BIO = 0.2, FIRE = 1, ACID = 0.2)
@@ -56,6 +62,8 @@ Unless you know what you're doing, only use the first three numbers. They're in
categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
sheet_type = /obj/item/stack/sheet/mineral/silver
value_per_unit = 50 / SHEET_MATERIAL_AMOUNT
+ tradable = TRUE
+ tradable_base_quantity = MATERIAL_QUANTITY_UNCOMMON
beauty_modifier = 0.075
/datum/material/silver/on_accidental_mat_consumption(mob/living/carbon/victim, obj/item/source_item)
@@ -72,6 +80,8 @@ Unless you know what you're doing, only use the first three numbers. They're in
categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
sheet_type = /obj/item/stack/sheet/mineral/gold
value_per_unit = 125 / SHEET_MATERIAL_AMOUNT
+ tradable = TRUE
+ tradable_base_quantity = MATERIAL_QUANTITY_RARE
beauty_modifier = 0.15
armor_modifiers = list(MELEE = 1.1, BULLET = 1.1, LASER = 1.15, ENERGY = 1.15, BOMB = 1, BIO = 1, FIRE = 0.7, ACID = 1.1)
@@ -90,6 +100,8 @@ Unless you know what you're doing, only use the first three numbers. They're in
alpha = 132
starlight_color = COLOR_BLUE_LIGHT
value_per_unit = 500 / SHEET_MATERIAL_AMOUNT
+ tradable = TRUE
+ tradable_base_quantity = MATERIAL_QUANTITY_EXOTIC
beauty_modifier = 0.3
armor_modifiers = list(MELEE = 1.3, BULLET = 1.3, LASER = 0.6, ENERGY = 1, BOMB = 1.2, BIO = 1, FIRE = 1, ACID = 1)
@@ -106,6 +118,8 @@ Unless you know what you're doing, only use the first three numbers. They're in
categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
sheet_type = /obj/item/stack/sheet/mineral/uranium
value_per_unit = 100 / SHEET_MATERIAL_AMOUNT
+ tradable = TRUE
+ tradable_base_quantity = MATERIAL_QUANTITY_RARE
beauty_modifier = 0.3 //It shines so beautiful
armor_modifiers = list(MELEE = 1.5, BULLET = 1.4, LASER = 0.5, ENERGY = 0.5, FIRE = 1, ACID = 1)
@@ -173,6 +187,8 @@ Unless you know what you're doing, only use the first three numbers. They're in
beauty_modifier = 0.5
sheet_type = /obj/item/stack/sheet/bluespace_crystal
value_per_unit = 300 / SHEET_MATERIAL_AMOUNT
+ tradable = TRUE
+ tradable_base_quantity = MATERIAL_QUANTITY_EXOTIC
/datum/material/bluespace/on_accidental_mat_consumption(mob/living/carbon/victim, obj/item/source_item)
victim.reagents.add_reagent(/datum/reagent/bluespace, rand(5, 8))
@@ -216,6 +232,8 @@ Unless you know what you're doing, only use the first three numbers. They're in
categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
sheet_type = /obj/item/stack/sheet/mineral/titanium
value_per_unit = 125 / SHEET_MATERIAL_AMOUNT
+ tradable = TRUE
+ tradable_base_quantity = MATERIAL_QUANTITY_UNCOMMON
beauty_modifier = 0.05
armor_modifiers = list(MELEE = 1.35, BULLET = 1.3, LASER = 1.3, ENERGY = 1.25, BOMB = 1.25, BIO = 1, FIRE = 0.7, ACID = 1)
diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm
index 95a95aa4bda..faa48559100 100644
--- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm
+++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm
@@ -1351,6 +1351,15 @@
greyscale_colors = CIRCUIT_COLOR_SUPPLY
build_path = /obj/machinery/rnd/production/techfab/department/cargo
+/obj/item/circuitboard/machine/materials_market
+ name = "Galactic Materials Market"
+ greyscale_colors = CIRCUIT_COLOR_SUPPLY
+ build_path = /obj/machinery/materials_market
+ req_components = list(
+ /obj/item/stack/cable_coil = 5,
+ /datum/stock_part/scanning_module = 1,
+ /datum/stock_part/card_reader = 1)
+
//Misc
/obj/item/circuitboard/machine/sheetifier
name = "Sheet-meister 2000"
diff --git a/code/modules/cargo/exports/materials.dm b/code/modules/cargo/exports/materials.dm
index 06c52305f51..46d089b5ac5 100644
--- a/code/modules/cargo/exports/materials.dm
+++ b/code/modules/cargo/exports/materials.dm
@@ -2,10 +2,13 @@
cost = 5 // Cost per SHEET_MATERIAL_AMOUNT, which is 100cm3 as of May 2023.
message = "cm3 of developer's tears. Please, report this on github"
amount_report_multiplier = SHEET_MATERIAL_AMOUNT
- var/material_id = null
+ var/datum/material/material_id = null
export_types = list(
- /obj/item/stack/sheet/mineral, /obj/item/stack/tile/mineral,
- /obj/item/stack/ore, /obj/item/coin)
+ /obj/item/stack/sheet/mineral,
+ /obj/item/stack/tile/mineral,
+ /obj/item/stack/ore,
+ /obj/item/coin
+ )
// Yes, it's a base type containing export_types.
// But it has no material_id, so any applies_to check will return false, and these types reduce amount of copypasta a lot
@@ -27,7 +30,14 @@
return round(amount / SHEET_MATERIAL_AMOUNT)
-// Materials. Nothing but plasma is really worth selling. Better leave it all to RnD and sell some plasma instead.
+// Materials. Static materials exist as parent types, while materials subject to the stock market have a fluid cost as determined by material/market types
+// If you're adding a new material to the stock market, make sure its export type is added here.
+
+/datum/export/material/plasma
+ cost = CARGO_CRATE_VALUE * 0.4
+ k_elasticity = 0
+ material_id = /datum/material/plasma
+ message = "cm3 of plasma"
/datum/export/material/bananium
cost = CARGO_CRATE_VALUE * 2
@@ -35,37 +45,6 @@
message = "cm3 of bananium"
/datum/export/material/diamond
- cost = CARGO_CRATE_VALUE
- material_id = /datum/material/diamond
- message = "cm3 of diamonds"
-
-/datum/export/material/plasma
- cost = CARGO_CRATE_VALUE * 0.4
- k_elasticity = 0
- material_id = /datum/material/plasma
- message = "cm3 of plasma"
-
-/datum/export/material/uranium
- cost = CARGO_CRATE_VALUE * 0.2
- material_id = /datum/material/uranium
- message = "cm3 of uranium"
-
-/datum/export/material/gold
- cost = CARGO_CRATE_VALUE * 0.25
- material_id = /datum/material/gold
- message = "cm3 of gold"
-
-/datum/export/material/silver
- cost = CARGO_CRATE_VALUE * 0.1
- material_id = /datum/material/silver
- message = "cm3 of silver"
-
-/datum/export/material/titanium
- cost = CARGO_CRATE_VALUE * 0.25
- material_id = /datum/material/titanium
- message = "cm3 of titanium"
-
-/datum/export/material/adamantine
cost = CARGO_CRATE_VALUE
material_id = /datum/material/adamantine
message = "cm3 of adamantine"
@@ -75,11 +54,6 @@
material_id = /datum/material/mythril
message = "cm3 of mythril"
-/datum/export/material/bscrystal
- cost = CARGO_CRATE_VALUE * 0.6
- message = "of bluespace crystals"
- material_id = /datum/material/bluespace
-
/datum/export/material/plastic
cost = CARGO_CRATE_VALUE * 0.05
message = "cm3 of plastic"
@@ -90,21 +64,6 @@
message = "cm3 of runite"
material_id = /datum/material/runite
-/datum/export/material/iron
- cost = CARGO_CRATE_VALUE * 0.01
- message = "cm3 of iron"
- material_id = /datum/material/iron
- export_types = list(
- /obj/item/stack/sheet/iron, /obj/item/stack/tile/iron,
- /obj/item/stack/rods, /obj/item/stack/ore, /obj/item/coin)
-
-/datum/export/material/glass
- cost = CARGO_CRATE_VALUE * 0.01
- message = "cm3 of glass"
- material_id = /datum/material/glass
- export_types = list(/obj/item/stack/sheet/glass, /obj/item/stack/ore,
- /obj/item/shard)
-
/datum/export/material/hot_ice
cost = CARGO_CRATE_VALUE * 0.8
message = "cm3 of Hot Ice"
@@ -116,3 +75,90 @@
message = "cm3 of metallic hydrogen"
material_id = /datum/material/metalhydrogen
export_types = /obj/item/stack/sheet/mineral/metal_hydrogen
+
+/datum/export/material/market
+
+/datum/export/material/market/diamond
+ material_id = /datum/material/diamond
+ message = "cm3 of diamonds"
+
+/datum/export/material/market/uranium
+ material_id = /datum/material/uranium
+ message = "cm3 of uranium"
+
+/datum/export/material/market/gold
+ material_id = /datum/material/gold
+ message = "cm3 of gold"
+
+/datum/export/material/market/silver
+ material_id = /datum/material/silver
+ message = "cm3 of silver"
+
+/datum/export/material/market/titanium
+ material_id = /datum/material/titanium
+ message = "cm3 of titanium"
+
+/datum/export/material/market/bscrystal
+ message = "of bluespace crystals"
+ material_id = /datum/material/bluespace
+ export_types = list(/obj/item/stack/sheet/bluespace_crystal, /obj/item/stack/ore) //For whatever reason, bluespace crystals are not a mineral
+
+/datum/export/material/market/iron
+ message = "cm3 of iron"
+ material_id = /datum/material/iron
+ export_types = list(
+ /obj/item/stack/sheet/iron,
+ /obj/item/stack/tile/iron,
+ /obj/item/stack/rods,
+ /obj/item/stack/ore,
+ /obj/item/coin
+ )
+
+/datum/export/material/market/glass
+ message = "cm3 of glass"
+ material_id = /datum/material/glass
+ export_types = list(
+ /obj/item/stack/sheet/glass,
+ /obj/item/stack/ore,
+ /obj/item/shard
+ )
+
+/datum/export/material/market/get_cost(obj/O, apply_elastic = FALSE)
+ var/obj/item/I = O
+ var/amount = get_amount(I)
+ if(!amount)
+ return 0
+ var/material_value = (SSstock_market.materials_prices[material_id]) * amount * MARKET_PROFIT_MODIFIER
+ return round(material_value)
+
+/datum/export/material/market/sell_object(obj/sold_item, datum/export_report/report, dry_run, apply_elastic)
+ . = ..()
+ var/amount = get_amount(sold_item)
+ var/price = get_cost(sold_item)
+ if(!amount)
+ return
+ if(!dry_run)
+ SSstock_market.materials_quantity[material_id] += amount
+ SSstock_market.materials_prices[material_id] -= round((price) * (amount / (amount + SSstock_market.materials_quantity[material_id])))
+ //This formula should impact lower quantity materials greater, and higher quantity materials less. Still, it's a bit rough. Tweaking may be needed.
+
+
+// Stock blocks are a special type of export that can be used to sell a quantity of materials at a specific price on the market.
+/datum/export/stock_block
+ cost = 0
+ message = "stock block"
+ export_types = list(/obj/item/stock_block)
+
+/datum/export/stock_block/get_cost(obj/O, apply_elastic = FALSE)
+ var/obj/item/stock_block/block = O
+ return block.export_value
+
+/datum/export/stock_block/sell_object(obj/sold_item, datum/export_report/report, dry_run, apply_elastic)
+ . = ..()
+ if(dry_run)
+ return
+ var/obj/item/stock_block/sold_block = sold_item
+ var/sale_value = sold_block.export_value
+ SSstock_market.materials_quantity[sold_block.export_mat] += sold_block.quantity
+ SSstock_market.materials_prices[sold_block.export_mat] -= round((sale_value) * (sold_block.quantity / (sold_block.quantity + SSstock_market.materials_quantity[sold_block.export_mat])))
+ SSstock_market.materials_prices[sold_block.export_mat] = round(clamp(SSstock_market.materials_prices[sold_block.export_mat], sold_block.export_mat.value_per_unit * SHEET_MATERIAL_AMOUNT * 0.5 , sold_block.export_mat.value_per_unit * SHEET_MATERIAL_AMOUNT * 3))
diff --git a/code/modules/cargo/materials_market.dm b/code/modules/cargo/materials_market.dm
new file mode 100644
index 00000000000..d211df7debd
--- /dev/null
+++ b/code/modules/cargo/materials_market.dm
@@ -0,0 +1,259 @@
+/obj/machinery/materials_market
+ name = "galactic materials market"
+ desc = "This machine allows the user to buy and sell sheets of minerals \
+ across the system. Prices are known to fluxuate quite often,\
+ sometimes even within the same minute. All transactions are final."
+ circuit = /obj/item/circuitboard/machine/materials_market
+ req_access = list(ACCESS_CARGO)
+ density = TRUE
+ icon = 'icons/obj/economy.dmi'
+ icon_state = "mat_market"
+ base_icon_state = "mat_market"
+ idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION
+ /// What items can be converted into a stock block? Must be a stack subtype based on current implementation.
+ var/list/exportable_material_items = list(
+ /obj/item/stack/sheet/iron, //God why are we like this
+ /obj/item/stack/sheet/glass, //No really, God why are we like this
+ /obj/item/stack/sheet/mineral,
+ /obj/item/stack/tile/mineral,
+ /obj/item/stack/ore,
+ /obj/item/stack/sheet/bluespace_crystal,
+ /obj/item/stack/rods
+ )
+ /// Are we ordering sheets from our own card balance or the cargo budget?
+ var/ordering_private = TRUE
+ /// Currently, can we order sheets from our own card balance or the cargo budget?
+ var/can_buy_via_budget = FALSE
+
+/obj/machinery/materials_market/update_icon_state()
+ if(panel_open)
+ icon_state = "[base_icon_state]_open"
+ return ..()
+ if(!is_operational || !anchored)
+ icon_state = "[base_icon_state]_off"
+ return ..()
+ icon_state = "[base_icon_state]"
+ return ..()
+
+/obj/machinery/materials_market/wrench_act(mob/living/user, obj/item/tool)
+ ..()
+ default_unfasten_wrench(user, tool, time = 1.5 SECONDS)
+ return TOOL_ACT_TOOLTYPE_SUCCESS
+
+/obj/machinery/materials_market/attackby(obj/item/O, mob/user, params)
+ if(default_deconstruction_screwdriver(user, "[base_icon_state]_open", "[base_icon_state]", O))
+ return
+ else if(default_deconstruction_crowbar(O))
+ return
+ if(is_type_in_list(O, exportable_material_items))
+ var/amount = 0
+ var/value = 0
+ var/material_to_export
+ var/obj/item/stack/exportable = O
+ for(var/datum/material/mat as anything in SSstock_market.materials_prices)
+ if(exportable.has_material_type(mat))
+ amount = exportable.amount
+ value = SSstock_market.materials_prices[mat]
+ material_to_export = mat
+ break //This is only for trading non-alloys, so we can break here
+
+ if(!amount)
+ say("Not enough material. Aborting.")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, FALSE)
+ return TRUE
+ qdel(exportable)
+ var/obj/item/stock_block/new_block = new /obj/item/stock_block(drop_location())
+ new_block.export_value = amount * value * MARKET_PROFIT_MODIFIER
+ new_block.export_mat = material_to_export
+ new_block.quantity = amount
+ to_chat(user, span_notice("You have created a stock block worth [new_block.export_value] cr! Sell it before it becomes liquid!"))
+ playsound(src, 'sound/machines/synth_yes.ogg', 50, FALSE)
+ return TRUE
+ return ..()
+
+
+/obj/machinery/materials_market/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!anchored)
+ return
+ if(!ui)
+ ui = new(user, src, "MatMarket", name)
+ ui.open()
+
+/obj/machinery/materials_market/ui_data(mob/user)
+ var/data = list()
+ var/material_data
+ for(var/datum/material/traded_mat as anything in SSstock_market.materials_prices)
+ var/trend_string = ""
+ if(SSstock_market.materials_trends[traded_mat] == 0)
+ trend_string = "neutral"
+ else if(SSstock_market.materials_trends[traded_mat] == 1)
+ trend_string = "up"
+ else if(SSstock_market.materials_trends[traded_mat] == -1)
+ trend_string = "down"
+ var/color_string = ""
+ if(traded_mat.color)
+ color_string = traded_mat.color
+ else if (traded_mat.greyscale_colors)
+ color_string = splicetext(traded_mat.greyscale_colors, 6, length(traded_mat.greyscale_colors), "") //slice it to a standard 6 char hex
+ material_data += list(list(
+ "name" = traded_mat.name,
+ "price" = SSstock_market.materials_prices[traded_mat],
+ "quantity" = SSstock_market.materials_quantity[traded_mat],
+ "trend" = trend_string,
+ "color" = color_string,
+ ))
+
+ can_buy_via_budget = FALSE
+ var/obj/item/card/id/used_id_card
+ if(isliving(user))
+ var/mob/living/living_user = user
+ used_id_card = living_user.get_idcard(TRUE)
+ can_buy_via_budget = (ACCESS_CARGO in used_id_card?.GetAccess())
+
+ var/balance = 0
+ if(!ordering_private)
+ var/datum/bank_account/dept = SSeconomy.get_dep_account(ACCOUNT_CAR)
+ if(dept)
+ balance = dept.account_balance
+ else
+ balance = used_id_card?.registered_account?.account_balance
+
+ var/market_crashing = FALSE
+ if(HAS_TRAIT(SSeconomy, TRAIT_MARKET_CRASHING))
+ market_crashing = TRUE
+
+ data["catastrophe"] = market_crashing
+ data["materials"] = material_data
+ data["creditBalance"] = balance
+ data["orderingPrive"] = ordering_private
+ data["canOrderCargo"] = can_buy_via_budget
+ return data
+
+/obj/machinery/materials_market/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ if(!isliving(usr))
+ return
+ switch(action)
+ if("buy")
+ var/material_str = params["material"]
+ var/quantity = text2num(params["quantity"])
+
+ var/datum/material/material_bought
+ var/obj/item/stack/sheet/sheet_to_buy
+ for(var/datum/material/mat as anything in SSstock_market.materials_prices)
+ if(mat.name == material_str)
+ material_bought = mat
+ break
+ if(!material_bought)
+ CRASH("Invalid material name passed to materials market!")
+ var/mob/living/living_user = usr
+ var/datum/bank_account/account_payable = SSeconomy.get_dep_account(ACCOUNT_CAR)
+ if(ordering_private)
+ var/obj/item/card/id/used_id_card = living_user.get_idcard(TRUE)
+ account_payable = used_id_card.registered_account
+ else if(can_buy_via_budget)
+ account_payable = SSeconomy.get_dep_account(ACCOUNT_CAR)
+
+ var/cost = SSstock_market.materials_prices[material_bought] * quantity
+
+ sheet_to_buy = material_bought.sheet_type
+ if(!sheet_to_buy)
+ CRASH("Material with no sheet type being sold on materials market!")
+ if(!account_payable)
+ say("No bank account detected!")
+ return
+ if(cost > account_payable.account_balance)
+ to_chat(living_user, span_warning("You don't have enough money to buy that!"))
+ return
+ var/list/things_to_order = list()
+ things_to_order += (sheet_to_buy)
+ things_to_order[sheet_to_buy] = quantity
+ // We want to count how many stacks of all sheets we're ordering to make sure they don't exceed the limit of 10
+ //If we already have a custom order on SSshuttle, we should add the things to order to that order
+ for(var/datum/supply_order/order in SSshuttle.shopping_list)
+ if(order.orderer == living_user && order.orderer_rank == "Galactic Materials Market")
+ var/prior_stacks = 0
+ for(var/obj/item/stack/sheet/sheet as anything in order.pack.contains)
+ prior_stacks += ROUND_UP(order.pack.contains[sheet] / 50)
+ if(prior_stacks >= 10)
+ to_chat(usr, span_notice("You already have 10 stacks of sheets on order! Please wait for them to arrive before ordering more."))
+ playsound(usr, 'sound/machines/synth_no.ogg', 35, FALSE)
+ return
+ order.append_order(things_to_order, cost)
+ account_payable.adjust_money(-(cost) , "Materials Market Purchase") //Add the extra price to the total
+ return
+ account_payable.adjust_money(-(CARGO_CRATE_VALUE) , "Materials Market Purchase") //Here is where we factor in the base cost of a crate
+ //Now we need to add a cargo order for quantity sheets of material_bought.sheet_type
+ var/datum/supply_pack/custom/minerals/mineral_pack = new(
+ purchaser = living_user, \
+ cost = SSstock_market.materials_prices[material_bought] * quantity, \
+ contains = things_to_order, \
+ )
+ var/datum/supply_order/new_order = new(
+ pack = mineral_pack,
+ orderer = living_user,
+ orderer_rank = "Galactic Materials Market",
+ orderer_ckey = living_user.ckey,
+ reason = "",
+ paying_account = account_payable,
+ department_destination = null,
+ coupon = null,
+ charge_on_purchase = FALSE,
+ manifest_can_fail = FALSE,
+ cost_type = "credit",
+ can_be_cancelled = FALSE,
+ )
+ say("Thank you for your purchase! It will arrive on the next cargo shuttle!")
+ SSshuttle.shopping_list += new_order
+ return
+ if("toggle_budget")
+ if(!can_buy_via_budget)
+ return
+ ordering_private = !ordering_private
+
+
+/obj/item/stock_block
+ name = "stock block"
+ desc = "A block of stock. It's worth a certain amount of money, based on a sale on the materials market. Ship it on the cargo shuttle to claim your money."
+ icon = 'icons/obj/economy.dmi'
+ icon_state = "stock_block"
+ /// How many credits was this worth when created?
+ var/export_value = 0
+ /// What is the name of the material this was made from?
+ var/datum/material/export_mat
+ /// Quantity of export material
+ var/quantity = 0
+ /// Is this stock block currently updating it's value with the market (aka fluid)?
+ var/fluid = FALSE
+
+/obj/item/stock_block/examine(mob/user)
+ . = ..()
+ . += span_notice("\The [src] is worth [export_value] cr, from selling [quantity] sheets of [export_mat?.name].")
+ if(fluid)
+ . += span_warning("\The [src] is currently liquid! It's value is based on the market price.")
+ else
+ . += span_notice("\The [src]'s value is still [span_boldnotice("locked in")]. [span_boldnotice("Sell it")] before it's value becomes liquid!")
+
+/obj/item/stock_block/Initialize(mapload)
+ . = ..()
+ addtimer(CALLBACK(src, PROC_REF(value_warning)), 2.5 MINUTES)
+ addtimer(CALLBACK(src, PROC_REF(update_value)), 5 MINUTES)
+
+/obj/item/stock_block/proc/value_warning()
+ visible_message(span_warning("\The [src] is starting to become liquid!"))
+ icon_state = "stock_block_fluid"
+ update_appearance(UPDATE_ICON_STATE)
+
+/obj/item/stock_block/proc/update_value()
+ if(!export_mat)
+ return
+ if(!SSstock_market.materials_prices[export_mat])
+ return
+ export_value = quantity * SSstock_market.materials_prices[export_mat] * MARKET_PROFIT_MODIFIER
+ icon_state = "stock_block_liquid"
+ update_appearance(UPDATE_ICON_STATE)
+ visible_message(span_warning("\The [src] becomes liquid!"))
+
diff --git a/code/modules/cargo/order.dm b/code/modules/cargo/order.dm
index 6c1f5e1d839..2707719c170 100644
--- a/code/modules/cargo/order.dm
+++ b/code/modules/cargo/order.dm
@@ -188,6 +188,15 @@
generateManifest(miscbox, misc_own, "", misc_cost)
return
+/datum/supply_order/proc/append_order(list/new_contents, cost_increase)
+ for(var/i as anything in new_contents)
+ if(pack.contains[i])
+ pack.contains[i] += new_contents[i]
+ else
+ pack.contains += i
+ pack.contains[i] = new_contents[i]
+ pack.cost += cost_increase
+
#undef MANIFEST_ERROR_CHANCE
#undef MANIFEST_ERROR_NAME
#undef MANIFEST_ERROR_CONTENTS
diff --git a/code/modules/cargo/packs/_packs.dm b/code/modules/cargo/packs/_packs.dm
index 4d7e5066a4a..aaeb55f2533 100644
--- a/code/modules/cargo/packs/_packs.dm
+++ b/code/modules/cargo/packs/_packs.dm
@@ -110,3 +110,15 @@
name = "[purchaser]'s Mining Order"
src.cost = cost
src.contains = contains
+
+/datum/supply_pack/custom/minerals
+ name = "materials order"
+ crate_name = "galactic materials market delivery crate"
+ access = list()
+ crate_type = /obj/structure/closet/crate/cardboard
+
+/datum/supply_pack/custom/minerals/New(purchaser, cost, list/contains)
+ . = ..()
+ name = "[purchaser]'s Materials Order"
+ src.cost = cost
+ src.contains = contains
diff --git a/code/modules/cargo/packs/imports.dm b/code/modules/cargo/packs/imports.dm
index 31e7c2b7f9e..6569321a164 100644
--- a/code/modules/cargo/packs/imports.dm
+++ b/code/modules/cargo/packs/imports.dm
@@ -299,3 +299,17 @@
contraband = TRUE
contains = list(/obj/item/weaponcrafting/giant_wrench)
crate_name = "unknown parts crate"
+
+/datum/supply_pack/imports/materials_market
+ name = "Galactic Materials Market Crate"
+ desc = "A circuit board to build your own materials market for use by certified market traders. Warning: Losses are not covered by insurance."
+ cost = CARGO_CRATE_VALUE * 3
+ contains = list(
+ /obj/item/circuitboard/machine/materials_market = 1,
+ /obj/item/stack/sheet/iron = 5,
+ /obj/item/stack/cable_coil/five = 2,
+ /obj/item/stock_parts/scanning_module = 1,
+ /obj/item/stock_parts/card_reader = 1
+ )
+ crate_name = "materials market crate"
+ crate_type = /obj/structure/closet/crate
diff --git a/code/modules/cargo/packs/materials.dm b/code/modules/cargo/packs/materials.dm
index 68dacd730be..ba9a162698b 100644
--- a/code/modules/cargo/packs/materials.dm
+++ b/code/modules/cargo/packs/materials.dm
@@ -16,34 +16,6 @@
contains = list(/obj/item/stack/license_plates/empty/fifty)
crate_name = "empty license plate crate"
-/datum/supply_pack/materials/glass50
- name = "50 Glass Sheets"
- desc = "Let some nice light in with fifty glass sheets!"
- cost = CARGO_CRATE_VALUE * 2
- contains = list(/obj/item/stack/sheet/glass/fifty)
- crate_name = "glass sheets crate"
-
-/datum/supply_pack/materials/iron50
- name = "50 Iron Sheets"
- desc = "Any construction project begins with a good stack of fifty iron sheets!"
- cost = CARGO_CRATE_VALUE * 2
- contains = list(/obj/item/stack/sheet/iron/fifty)
- crate_name = "iron sheets crate"
-
-/datum/supply_pack/materials/plasteel20
- name = "20 Plasteel Sheets"
- desc = "Reinforce the station's integrity with twenty plasteel sheets!"
- cost = CARGO_CRATE_VALUE * 15
- contains = list(/obj/item/stack/sheet/plasteel/twenty)
- crate_name = "plasteel sheets crate"
-
-/datum/supply_pack/materials/plasteel50
- name = "50 Plasteel Sheets"
- desc = "For when you REALLY have to reinforce something."
- cost = CARGO_CRATE_VALUE * 33
- contains = list(/obj/item/stack/sheet/plasteel/fifty)
- crate_name = "plasteel sheets crate"
-
/datum/supply_pack/materials/plastic50
name = "50 Plastic Sheets"
desc = "Build a limitless amount of toys with fifty plastic sheets!"
diff --git a/code/modules/cargo/packs/stock_market_items.dm b/code/modules/cargo/packs/stock_market_items.dm
new file mode 100644
index 00000000000..04b2eac4acf
--- /dev/null
+++ b/code/modules/cargo/packs/stock_market_items.dm
@@ -0,0 +1,36 @@
+/**
+ * todo: make this a supply_pack/custom. Drop pog? ohoho yes. Would be VERY fun.
+ */
+/datum/supply_pack/market_materials
+ name = "A Single Sheet of Bananium"
+ desc = "Going market price for this kind of sheet, by Australicus Industrial Mining."
+ cost = CARGO_CRATE_VALUE * 2
+ // contains = list(/obj/item/stack/sheet/mineral/bananium)
+ crate_name = "mineral stock sheet crate"
+ group = "Canisters & Materials"
+ /// What material we are trying to buy sheets of?
+ var/datum/material/material
+ /// How many sheets of the material we are trying to buy at once?
+ var/amount
+
+/datum/supply_pack/market_materials/get_cost()
+ for(var/datum/material/mat in SSstock_market.materials_prices)
+ if(material == mat)
+ return SSstock_market.materials_prices[mat] * amount
+
+/datum/supply_pack/market_materials/fill(obj/structure/closet/crate/C)
+ . = ..()
+ new material.sheet_type(C, amount)
+
+/datum/supply_pack/market_materials/iron
+ name = "Iron Sheets"
+ crate_name = "iron stock crate"
+ material = /datum/material/iron
+MARKET_QUANTITY_HELPERS(/datum/supply_pack/market_materials/iron)
+
+
+/datum/supply_pack/market_materials/gold
+ name = "Gold Sheets"
+ crate_name = "gold stock crate"
+ material = /datum/material/gold
+MARKET_QUANTITY_HELPERS(/datum/supply_pack/market_materials/gold)
diff --git a/icons/obj/economy.dmi b/icons/obj/economy.dmi
index dc90265b6e9..04abc41cae1 100644
Binary files a/icons/obj/economy.dmi and b/icons/obj/economy.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index bc2c5de3e86..35b9c7c2dcc 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -646,6 +646,7 @@
#include "code\controllers\subsystem\speech_controller.dm"
#include "code\controllers\subsystem\statpanel.dm"
#include "code\controllers\subsystem\stickyban.dm"
+#include "code\controllers\subsystem\stock_market.dm"
#include "code\controllers\subsystem\sun.dm"
#include "code\controllers\subsystem\tcgsetup.dm"
#include "code\controllers\subsystem\tgui.dm"
@@ -3317,6 +3318,7 @@
#include "code\modules\cargo\expressconsole.dm"
#include "code\modules\cargo\gondolapod.dm"
#include "code\modules\cargo\goodies.dm"
+#include "code\modules\cargo\materials_market.dm"
#include "code\modules\cargo\order.dm"
#include "code\modules\cargo\orderconsole.dm"
#include "code\modules\cargo\supplypod.dm"
@@ -3376,6 +3378,7 @@
#include "code\modules\cargo\packs\science.dm"
#include "code\modules\cargo\packs\security.dm"
#include "code\modules\cargo\packs\service.dm"
+#include "code\modules\cargo\packs\stock_market_items.dm"
#include "code\modules\cargo\packs\vending_restock.dm"
#include "code\modules\chatter\chatter.dm"
#include "code\modules\client\client_colour.dm"
diff --git a/tgui/packages/tgui/interfaces/MatMarket.tsx b/tgui/packages/tgui/interfaces/MatMarket.tsx
new file mode 100644
index 00000000000..86f44462cb1
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/MatMarket.tsx
@@ -0,0 +1,180 @@
+import { useBackend } from '../backend';
+import { Section, Stack, Button, Modal } from '../components';
+import { Window } from '../layouts';
+import { BooleanLike } from 'common/react';
+import { toTitleCase } from 'common/string';
+
+type Data = {
+ orderingPrive: BooleanLike; // you will need to import this
+ canOrderCargo: BooleanLike;
+ creditBalance: number;
+ materials: Material[];
+ catastrophe: BooleanLike;
+};
+
+type Material = {
+ name: string;
+ quantity: number;
+ id: string; // correct this if its a number
+ trend: string;
+ price: number;
+ color: string;
+};
+
+export const MatMarket = (props, context) => {
+ const { act, data } = useBackend(context); // this will tell your editor that data is the type listed above
+
+ const {
+ orderingPrive,
+ canOrderCargo,
+ creditBalance,
+ materials = [],
+ catastrophe,
+ } = data; // better to destructure here (style nit)
+ return (
+
+
+ {!!catastrophe && }
+ act('toggle_budget')}
+ />
+ }>
+ 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: {creditBalance || 'zero'} cr.
+
+
+ {materials.map((material) => (
+
+
+
+
+
+ {toTitleCase(material.name)}
+
+
+
+ Trading at {material.price} cr.
+
+
+
+ {material.quantity} sheets of {material.name}{' '}
+ trading.
+
+
+ {toTitleCase(material.name)} is trending{' '}
+ {material.trend}.
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+ );
+};
+
+const MarketCrashModal = (props, context) => {
+ const { act, data } = useBackend(context);
+ return (
+
+ ATTENTION! THE MARKET HAS CRASHED
+
+ ALL MATERIALS ARE NOW WORTHLESS
+
+ TRADING CIRCUIT BREAKER HAS BEEN ENGAGED FOR ALL TRADERS
+