From fb58e999342a7db03510b0a99b66e153de5eda2f Mon Sep 17 00:00:00 2001 From: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com> Date: Mon, 2 Mar 2026 17:42:04 -0500 Subject: [PATCH] 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. --- code/__DEFINES/market.dm | 21 ++++++++++ code/controllers/subsystem/market.dm | 29 +++++++++++-- code/game/atoms_movable.dm | 3 ++ .../structures/crates_lockers/crates.dm | 22 ++++++++++ code/modules/admin/admin_verbs.dm | 42 +++++++++++++++++++ code/modules/cargo/markets/_market.dm | 4 +- 6 files changed, 115 insertions(+), 6 deletions(-) diff --git a/code/__DEFINES/market.dm b/code/__DEFINES/market.dm index e0bd4578352..7c8427f4691 100644 --- a/code/__DEFINES/market.dm +++ b/code/__DEFINES/market.dm @@ -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,\ +) diff --git a/code/controllers/subsystem/market.dm b/code/controllers/subsystem/market.dm index a56ad90b1fd..8d165bee25d 100644 --- a/code/controllers/subsystem/market.dm +++ b/code/controllers/subsystem/market.dm @@ -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) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 8b5d7911504..14b4fa73ba8 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -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 diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index f293b86e8e5..545d7412773 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -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) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index a2ea441d823..5704a111195 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -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") diff --git a/code/modules/cargo/markets/_market.dm b/code/modules/cargo/markets/_market.dm index 4212ac63d86..78f2d7bd1a3 100644 --- a/code/modules/cargo/markets/_market.dm +++ b/code/modules/cargo/markets/_market.dm @@ -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