Admins can add new custom blackmarket listings, and tweaks the "Launch" shipping option. (#95191)

## About The Pull Request

Two main items:

1: Admins can now use the new "Create new Blackmarket Item" verb, which
prompts them about the item, price, quantity, name and description of
the item they'd like to list. If successful, the item will be listed to
the black market, available to purchase by the crew.

Prices can be set very, very high, though I've limited the available
quantity to 100 per listing as a nice middle-ground value. The verb can
be used any number of times, allowing for admins to use the black market
to run events, as such. Of note, this verb will spawn a fresh typepath
of the object listed, so it cannot be used to list var-edited objects
as-is, but it's still quite useful for offering unique or rare items to
the crew based on the needs of the round.


2: This PR does a few tweaks to the "launch" shipping option on the
black market. The text displayed when having an object launched to the
station has been adjusted to reduce some ambiguity on if the item is
potentially deleted when launched. The launch behavior has not been
modified in this way, as the object is still launched at the station
using similar logic as before.

**However**, the item is now launched to the station in a faux-energy
bubble, which destroys itself and empties it's contents when it makes
thrown impact or is opened by hand. In practice, this is only noticeable
if you were outside when the order is launched at the station, or if the
crate makes impact with you. This allows for black market objects that
create spawners to arrive at the station harmlessly, which also
potentially reducing some of the randomness of an object being thrown at
the station, and then leaving the z-level immediately based on the extra
collision involved. Additionally, I've lowered the **shipping cost to
0** of launched items.

Misc:
Did some code cleanup by adding defines for each of the categories of
black market items, and a list-define for every category together.

## Why It's Good For The Game

I've wanted to have the option for admins to be able to list things for
the crew to buy for quite awhile now, without requiring them to barter
by hand or through a reliable means that can't be bypassed by 3
assistants with baseball bats. This offers that option, while also
having some flavor of being offered on the black market, if that's
desirable. Otherwise, it's no impact on the average round as it's
admin-only.

Regarding the launch options, this started as a grammar tweak as I was
talking with Arm on discord and the lack of clarity raised the whole
shipping option to our attention. After confirming that launching things
to the station was NOT, in fact a literal noob trap, just an awful
choice, I started work on the shield-bubble idea in an attempt to see if
I could try and reduce some of the randomness involved with that
shipping option. It already sucks due to the risk of going out into
space, there's no need to make it suck worse by potentially losing any
of the cooler items available on the black market at the same time. So,
price down and a mild consistency up. I also ran into #95183 while I was
testing all of this, so this should fix #95183.

Lastly, code cleanup. Made a few things look 3% nicer.
This commit is contained in:
ArcaneMusic
2026-03-02 16:42:04 -06:00
committed by GitHub
parent e651df479b
commit fb58e99934
6 changed files with 115 additions and 6 deletions
+21
View File
@@ -12,3 +12,24 @@
/// The percentage on gains that's removed when selling an item through the blackmarket with the LTSRBT
#define MARKET_WITHHOLDING_TAX 0.15
//Black Market Uplink categories
#define BLACKMARKET_CATEGORY_CLOTHING "Clothing"
#define BLACKMARKET_CATEGORY_CONSUMABLES "Consumables"
#define BLACKMARKET_CATEGORY_HOSTAGES "Hostages"
#define BLACKMARKET_CATEGORY_LOCAL_GOODS "Local Goods"
#define BLACKMARKET_CATEGORY_MISC "Miscellaneous"
#define BLACKMARKET_CATEGORY_FENCED_GOODS "Fenced Goods"
#define BLACKMARKET_CATEGORY_TOOLS "Tools"
#define BLACKMARKET_CATEGORY_WEAPONS "Weapons"
#define BLACKMARKET_CATEGORIES list(\
BLACKMARKET_CATEGORY_CLOTHING,\
BLACKMARKET_CATEGORY_CONSUMABLES,\
BLACKMARKET_CATEGORY_HOSTAGES,\
BLACKMARKET_CATEGORY_LOCAL_GOODS,\
BLACKMARKET_CATEGORY_MISC,\
BLACKMARKET_CATEGORY_FENCED_GOODS,\
BLACKMARKET_CATEGORY_TOOLS,\
BLACKMARKET_CATEGORY_WEAPONS,\
)
+25 -4
View File
@@ -7,7 +7,7 @@ SUBSYSTEM_DEF(market)
/// Descriptions for each shipping methods.
var/shipping_method_descriptions = list(
SHIPPING_METHOD_LAUNCH = "Launches the item at the station from space, cheap but you might not receive your item at all.",
SHIPPING_METHOD_LAUNCH = "Launches the item at the station from space, with free shipping! However, you'll need to risk a spacewalk to receive your goods.",
SHIPPING_METHOD_LTSRBT = "Long-To-Short-Range-Bluespace-Transceiver, a machine that receives items outside the station and then teleports them to the location of the uplink.",
SHIPPING_METHOD_TELEPORT = "Teleports the item in a random area in the station, you get 60 seconds to get there first though.",
SHIPPING_METHOD_SUPPLYPOD = "Ships the item inside a supply pod at your exact location. Showy, speedy and expensive.",
@@ -19,6 +19,8 @@ SUBSYSTEM_DEF(market)
var/list/obj/machinery/ltsrbt/telepads = list()
/// Currently queued purchases.
var/list/queued_purchases = list()
/// How many admin items have been spawned this round? Used to iterate the identifier of admin-created market items.
var/admin_items_spawned = 0
/datum/controller/subsystem/market/Initialize()
for(var/market in subtypesof(/datum/market))
@@ -29,6 +31,11 @@ SUBSYSTEM_DEF(market)
return SS_INIT_SUCCESS
/**
* Follows the standard process for adding an item to the market from a path.
* @param path The path of the market datum to initialize and add to the market.
* @param market_whitelist A list of markets to which the item should be added. If null, the item is added to all markets.
*/
/datum/controller/subsystem/market/proc/initialize_item(datum/market_item/path, list/market_whitelist)
if(!path::item || !prob(path::availability_prob))
return
@@ -40,6 +47,19 @@ SUBSYSTEM_DEF(market)
if(isnull(market_whitelist) || (potential_market in market_whitelist))
markets[potential_market].add_item(item_instance)
/**
* Adds an admin polled item to the market, expecting a market_item object having been created.
*/
/datum/controller/subsystem/market/proc/initialize_admin_item(datum/market_item/item_instance, list/market_whitelist)
if(!item_instance)
return
for(var/potential_market in item_instance.markets)
if(!markets[potential_market])
stack_trace("SSmarket: Item [item_instance] available in market that does not exist.")
continue
if(isnull(market_whitelist) || (potential_market in market_whitelist))
markets[potential_market].add_item(item_instance)
/datum/controller/subsystem/market/fire(resumed)
while(length(queued_purchases))
var/datum/market_purchase/purchase = queued_purchases[1]
@@ -72,7 +92,7 @@ SUBSYSTEM_DEF(market)
// Get random area, throw it somewhere there.
if(SHIPPING_METHOD_TELEPORT)
var/turf/targetturf = get_safe_random_station_turf_equal_weight()
var/turf/targetturf = get_safe_random_station_turf_equal_weight() //todo: split weights
// This shouldn't happen.
if (!targetturf)
continue
@@ -88,10 +108,11 @@ SUBSYSTEM_DEF(market)
var/startSide = pick(GLOB.cardinals)
var/turf/T = get_turf(purchase.uplink)
var/pickedloc = spaceDebrisStartLoc(startSide, T.z)
var/obj/delivery_pod = new /obj/structure/closet/crate/market(pickedloc)
var/atom/movable/item = purchase.entry.spawn_item(pickedloc, purchase)
var/atom/movable/item = purchase.entry.spawn_item(delivery_pod, purchase)
purchase.post_purchase_effects(item)
item.throw_at(purchase.uplink, 3, 3, spin = FALSE)
delivery_pod.throw_at(purchase.uplink, 3, 3, spin = FALSE)
to_chat(buyer, span_notice("[purchase.uplink] flashes a message noting the order is being launched at the station from [dir2text(startSide)]."))
qdel(purchase)
+3
View File
@@ -1307,6 +1307,9 @@
explosive_resistance += explosion_block
SEND_SIGNAL(src, COMSIG_MOVABLE_EXPLOSION_BLOCK_CHANGED, old_block, explosion_block)
/**
* This proc is called when a thrown object makes contact with it's target. It then follows up by calling hitby below.
*/
/atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
set waitfor = FALSE
var/hitpush = TRUE
@@ -500,3 +500,25 @@
icon_state = "lavender"
base_icon_state = "lavender"
glitter_color = "#db80ff"
/obj/structure/closet/crate/market
name = "shield bubble"
desc = "A rippling blue energy bubble, capable of sustaining itself until it hits a solid wall."
icon = 'icons/effects/effects.dmi'
icon_state = "shield2"
base_icon_state = "shield2"
/obj/structure/closet/crate/market/after_open(mob/living/user, force)
. = ..()
visible_message(span_notice("[src] pops as [user] touches it!"))
pop_crate()
/obj/structure/closet/crate/market/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
. = ..()
pop_crate()
/// Called when the bubble either arrives at the station, or is interacted with someone/something.
/obj/structure/closet/crate/market/proc/pop_crate()
do_sparks(1, TRUE, get_turf(src))
dump_contents()
qdel(src)
+42
View File
@@ -806,3 +806,45 @@ ADMIN_VERB(give_ai_speech, R_FUN, "Give Random AI Speech", ADMIN_VERB_NO_DESCRIP
if (behaviour_exists)
return
our_controller.planning_subtrees = list(GLOB.ai_subtrees[/datum/ai_planning_subtree/random_speech/blackboard]) + our_controller.planning_subtrees
ADMIN_VERB(new_blackmarket_item, R_BUILD, "Create Black Market Item", "Add an item to the black market for purchase.", ADMIN_CATEGORY_EVENTS, object as text)
//first: have admins select a typepath for the item they want to offer.
var/obj/chosen = pick_closest_path(object, make_types_fancy(subtypesof(/obj)))
// second: poll admins for the name, description, price, and quantity.
var/name = tgui_input_text(user, "Name of the item to sell?", "Item listing name", "Arcane Object", max_length = MAX_NAME_LEN)
if(isnull(name))
return
var/description = tgui_input_text(user, "Custom description of the item to sell?", "Item listing description", "[chosen::desc]", max_length = 200)
if(isnull(description))
return
var/price = tgui_input_number(user, "Price for the item listing?", "Item listing price", max_value = INFINITY, min_value = 1, round_value = TRUE)
if(isnull(price))
return
var/quantity = tgui_input_number(user, "Quantity of the item to sell?", "Item listing quantity", default = 1, max_value = 100, min_value = 1, round_value = TRUE)
if(isnull(quantity))
return
//lastly: pick a category for the item to go under
var/category = tgui_input_list(user, "Category to list the item under?", "Item listing category", BLACKMARKET_CATEGORIES)
if(isnull(category))
return
var/datum/market_item/admin_item = new /datum/market_item()
// Making a note here that we don't need to assign to blackmarket because we still only have one market type, but if we ever start using multiple we'll want to poll admins.
admin_item.item = chosen
SSblackbox.record_feedback("tally", "admin blackmarket items", 1, chosen)
admin_item.name = name
admin_item.desc = description
admin_item.price = price
admin_item.stock = quantity
admin_item.category = category
admin_item.restockable = FALSE
SSmarket.admin_items_spawned++
admin_item.identifier = "admin_[SSmarket.admin_items_spawned]"
SSmarket.initialize_admin_item(admin_item)
log_admin("[key_name(user)] created a new black market item: [name] ([chosen]) for [price] credits, of quantity [quantity].")
BLACKBOX_LOG_ADMIN_VERB("Create Black Market Item")
+2 -2
View File
@@ -91,7 +91,7 @@
name = "Black Market"
shipping = list(
SHIPPING_METHOD_LTSRBT = 40,
SHIPPING_METHOD_LAUNCH = 10,
SHIPPING_METHOD_TELEPORT= 75,
SHIPPING_METHOD_LAUNCH = 0,
SHIPPING_METHOD_TELEPORT = 75,
)
legal_status = FALSE