From 8dec82ee857f86d71c7caa78c2abc63f5f829d31 Mon Sep 17 00:00:00 2001
From: SkyratBot <59378654+SkyratBot@users.noreply.github.com>
Date: Fri, 29 Mar 2024 03:39:23 +0100
Subject: [PATCH] [MIRROR] Blackmarket refactor and balance, fixing spy
bounties, plus a new category for people captured by
pirates/tots/contractors. (#26869)
Blackmarket refactor and balance, fixing spy bounties, plus a new category for people captured by pirates/tots/contractors. (#81818)
This PR aims to take care of a few potential hard dels and fix the
fenced goods category first and foremost.
The PR also adds a new one that enables you to buy mobs captured by
antags in advance, before they're sent back to the station. Unlike other
categories, it doesn't have "Launch" and "Teleport" as delivery methods,
instead it uses a special "Supply Pod" delivery method that ships the
chattel directly to your location. This method costs 400 credits,
however, if you've built the LTSRBT, shipment will be free.
Another thing, I always felt LTSRBT to be pretty fucking dumb as is. Way
too overpriced for the benefits it offers: a slighty cheaper delivery
method that, while definitely better than the other two, doesn't really
pay off or honestly even remotely compensate the 4000 credits you just
spent to get it, especially if you're only buying a couple items at
most. So I decided to remove it from cargo and add it to the blackmarket
as a 500 to 750 creds item, available every round.
Human mobs sold by the pirate bounty pad are no longer deleted, instead
they're properly ransomed and sent to the holding facility, much like
for contractors and tots. This means they're also added to the black
market.
The blackmarket system has some issues to it that need to be fixed
(otherwise the "Fenced Goods" and "Hostages" categories wouldn't work).
The Pirate Pad deleting "ransomed" mobs instead of actually ransoming
them is lame. The LTSRBT shouldn't suck as much. Also more market stuff.
This will fix #81809.
:cl:
fix: Fixed the "Fenced Goods" black market category.
balance: Removed the LTSRBT from cargo and added it to the blackmarket,
reduced the price from 4000 to 625 on average.
balance: The time it takes for captured mobs to be automatically sent
back to the station from the holding facility has been increased from
3-4 minutes to 6.
add: You can buy mobs captured by contractors, traitors and pirates from
the black market and have them sent back to the station in advance. For
safety, they'll also be handcuffed (not always) upon delivery.
add: Human mobs sold by pirates are not deleted anymore. Instead,
they're now captured and sent to the holding facility.
/:cl:
Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
---
code/__DEFINES/antagonists.dm | 3 +
code/__DEFINES/blackmarket.dm | 3 +-
code/__DEFINES/cargo.dm | 7 ++
.../dcs/signals/signals_blackmarket.dm | 2 +
.../signals/signals_mob/signals_mob_living.dm | 3 +
code/controllers/subsystem/blackmarket.dm | 65 +++++++-------
code/game/gamemodes/objective.dm | 5 ++
.../pirate/pirate_shuttle_equipment.dm | 74 +++++++++++++---
code/modules/antagonists/spy/spy_bounty.dm | 36 ++++----
.../traitor/contractor/syndicate_contract.dm | 68 ++++++---------
.../traitor/objectives/kidnapping.dm | 46 ++++------
code/modules/cargo/exports.dm | 65 +++++++++-----
code/modules/cargo/markets/_market.dm | 68 +++++++++------
code/modules/cargo/markets/market_item.dm | 73 +++++++++++++---
.../cargo/markets/market_items/consumables.dm | 3 +-
.../cargo/markets/market_items/hostages.dm | 84 +++++++++++++++++++
.../cargo/markets/market_items/misc.dm | 6 +-
.../markets/market_items/stolen_goods.dm | 12 +++
.../cargo/markets/market_items/tools.dm | 12 +++
code/modules/cargo/markets/market_telepad.dm | 50 +++++------
code/modules/cargo/markets/market_uplink.dm | 31 ++++---
code/modules/cargo/packs/imports.dm | 13 ---
code/modules/cargo/supplypod.dm | 18 ++++
.../clothing/under/accessories/badges.dm | 4 +-
code/modules/mob/living/living.dm | 20 +++++
tgstation.dme | 3 +
26 files changed, 524 insertions(+), 250 deletions(-)
create mode 100644 code/__DEFINES/dcs/signals/signals_blackmarket.dm
create mode 100644 code/modules/cargo/markets/market_items/hostages.dm
create mode 100644 code/modules/cargo/markets/market_items/stolen_goods.dm
diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index e3e67c82b57..d5533dcac54 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -36,6 +36,9 @@
///How long pirates will wait for a response before attacking
#define RESPONSE_MAX_TIME 2 MINUTES
+/// How long till a spessman should come back after being captured and sent to the holding facility (which some antags use)
+#define COME_BACK_FROM_CAPTURE_TIME 6 MINUTES
+
//ERT Types
#define ERT_BLUE "Blue"
#define ERT_RED "Red"
diff --git a/code/__DEFINES/blackmarket.dm b/code/__DEFINES/blackmarket.dm
index 5494c371db7..c3b8ad0bf46 100644
--- a/code/__DEFINES/blackmarket.dm
+++ b/code/__DEFINES/blackmarket.dm
@@ -7,4 +7,5 @@
#define SHIPPING_METHOD_TELEPORT "Teleport"
// Throws the item from somewhere at the station.
#define SHIPPING_METHOD_LAUNCH "Launch"
-
+// Sends a supply pod to the buyer's location, showy.
+#define SHIPPING_METHOD_SUPPLYPOD "Supply Pod"
diff --git a/code/__DEFINES/cargo.dm b/code/__DEFINES/cargo.dm
index 63d5682ef0f..1d34ed6b3cf 100644
--- a/code/__DEFINES/cargo.dm
+++ b/code/__DEFINES/cargo.dm
@@ -64,3 +64,10 @@
#define SUPPLY_PACK_UNCOMMON_DISCOUNTABLE "uncommon_discount"
///Discount category for the silly, overpriced, joke content, sometimes useful or plain bad.
#define SUPPLY_PACK_RARE_DISCOUNTABLE "rare_discount"
+
+///Standard export define for not selling the item.
+#define EXPORT_NOT_SOLD 0
+///Sell the item
+#define EXPORT_SOLD 1
+///Sell the item, but for the love of god, don't delete it, we're handling it in a fancier way.
+#define EXPORT_SOLD_DONT_DELETE 2
diff --git a/code/__DEFINES/dcs/signals/signals_blackmarket.dm b/code/__DEFINES/dcs/signals/signals_blackmarket.dm
new file mode 100644
index 00000000000..86e3d9277a1
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_blackmarket.dm
@@ -0,0 +1,2 @@
+///From /datum/market_item/spawn_item(): (uplink, shipping_method, shipping_loc)
+#define COMSIG_MARKET_ITEM_SPAWNED "market_item_spawned"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
index e411cdfba92..924488af73e 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
@@ -174,6 +174,9 @@
///From obj/item/toy/crayon/spraycan
#define COMSIG_LIVING_MOB_PAINTED "living_mob_painted"
+///From obj/closet/supplypod/return_victim: (turf/destination)
+#define COMSIG_LIVING_RETURN_FROM_CAPTURE "living_return_from_capture"
+
///From mob/living/proc/wabbajack(): (randomize_type)
#define COMSIG_LIVING_PRE_WABBAJACKED "living_mob_wabbajacked"
/// Return to stop the rest of the wabbajack from triggering.
diff --git a/code/controllers/subsystem/blackmarket.dm b/code/controllers/subsystem/blackmarket.dm
index bdd342cbf3d..ea70b5c7229 100644
--- a/code/controllers/subsystem/blackmarket.dm
+++ b/code/controllers/subsystem/blackmarket.dm
@@ -5,9 +5,10 @@ SUBSYSTEM_DEF(blackmarket)
/// 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_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_LAUNCH = "Launches the item at the station from space, cheap but you might not receive your item at all.",
+ 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.",
)
/// List of all existing markets.
@@ -42,11 +43,7 @@ SUBSYSTEM_DEF(blackmarket)
var/datum/market_purchase/purchase = queued_purchases[1]
queued_purchases.Cut(1,2)
- // Uh oh, uplink is gone. We will just keep the money and you will not get your order.
- if(!purchase.uplink || QDELETED(purchase.uplink))
- queued_purchases -= purchase
- qdel(purchase)
- continue
+ var/mob/buyer = recursive_loc_check(purchase.uplink.loc, /mob)
switch(purchase.method)
// Find a ltsrbt pad and make it handle the shipping.
@@ -54,65 +51,71 @@ SUBSYSTEM_DEF(blackmarket)
if(!telepads.len)
continue
// Prioritize pads that don't have a cooldown active.
- var/free_pad_found = FALSE
- for(var/obj/machinery/ltsrbt/pad in telepads)
+ var/obj/machinery/ltsrbt/free_pad_found
+ for(var/obj/machinery/ltsrbt/pad as anything in telepads)
if(pad.recharge_cooldown)
continue
pad.add_to_queue(purchase)
- queued_purchases -= purchase
- free_pad_found = TRUE
+ free_pad_found = pad
break
- if(free_pad_found)
+ if(isnull(free_pad_found))
continue
- var/obj/machinery/ltsrbt/pad = pick(telepads)
+ to_chat(buyer, span_notice("[purchase.uplink] flashes a message noting that the order is being processed by [free_pad_found]."))
- to_chat(recursive_loc_check(purchase.uplink.loc, /mob), span_notice("[purchase.uplink] flashes a message noting that the order is being processed by [pad]."))
-
- queued_purchases -= purchase
- pad.add_to_queue(purchase)
// Get random area, throw it somewhere there.
if(SHIPPING_METHOD_TELEPORT)
var/turf/targetturf = get_safe_random_station_turf()
// This shouldn't happen.
if (!targetturf)
continue
+ queued_purchases -= purchase
- to_chat(recursive_loc_check(purchase.uplink.loc, /mob), span_notice("[purchase.uplink] flashes a message noting that the order is being teleported to [get_area(targetturf)] in 60 seconds."))
+ to_chat(buyer, span_notice("[purchase.uplink] flashes a message noting that the order is being teleported to [get_area(targetturf)] in 60 seconds."))
// do_teleport does not want to teleport items from nullspace, so it just forceMoves and does sparks.
- addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/controller/subsystem/blackmarket,fake_teleport), purchase.entry.spawn_item(), targetturf), 60 SECONDS)
- queued_purchases -= purchase
- qdel(purchase)
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/controller/subsystem/blackmarket,fake_teleport), purchase, targetturf), 60 SECONDS)
+
// Get the current location of the uplink if it exists, then throws the item from space at the station from a random direction.
if(SHIPPING_METHOD_LAUNCH)
var/startSide = pick(GLOB.cardinals)
var/turf/T = get_turf(purchase.uplink)
var/pickedloc = spaceDebrisStartLoc(startSide, T.z)
- var/atom/movable/item = purchase.entry.spawn_item(pickedloc)
+ var/atom/movable/item = purchase.entry.spawn_item(pickedloc, purchase)
item.throw_at(purchase.uplink, 3, 3, spin = FALSE)
- to_chat(recursive_loc_check(purchase.uplink.loc, /mob), span_notice("[purchase.uplink] flashes a message noting the order is being launched at the station from [dir2text(startSide)]."))
+ to_chat(buyer, span_notice("[purchase.uplink] flashes a message noting the order is being launched at the station from [dir2text(startSide)]."))
+ qdel(purchase)
- queued_purchases -= purchase
+ if(SHIPPING_METHOD_SUPPLYPOD)
+ var/obj/structure/closet/supplypod/back_to_station/pod = new()
+ purchase.entry.spawn_item(pod, purchase)
+ new /obj/effect/pod_landingzone(get_turf(purchase.uplink), pod)
+
+ to_chat(buyer, span_notice("[purchase.uplink] flashes a message noting the order is being launched at your location. Right here, right now!"))
qdel(purchase)
if(MC_TICK_CHECK)
break
/// Used to make a teleportation effect as do_teleport does not like moving items from nullspace.
-/datum/controller/subsystem/blackmarket/proc/fake_teleport(atom/movable/item, turf/target)
- item.forceMove(target)
+/datum/controller/subsystem/blackmarket/proc/fake_teleport(datum/market_purchase/purchase, turf/target)
+ // Oopsie, whoopsie, the item is gone. So long, and thanks for all the money.
+ if(QDELETED(purchase))
+ return
+ var/atom/movable/thing = purchase.entry.spawn_item(target, purchase)
var/datum/effect_system/spark_spread/sparks = new
sparks.set_up(5, 1, target)
- sparks.attach(item)
+ sparks.attach(thing)
sparks.start()
+ qdel(purchase)
/// Used to add /datum/market_purchase to queued_purchases var. Returns TRUE when queued.
-/datum/controller/subsystem/blackmarket/proc/queue_item(datum/market_purchase/P)
- if(P.method == SHIPPING_METHOD_LTSRBT && !telepads.len)
+/datum/controller/subsystem/blackmarket/proc/queue_item(datum/market_purchase/purchase)
+ if((purchase.method == SHIPPING_METHOD_LTSRBT && !telepads.len) || isnull(purchase.uplink))
+ qdel(purchase)
return FALSE
- queued_purchases += P
+ queued_purchases += purchase
return TRUE
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index f04ff43553c..d81f9306af4 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -1017,6 +1017,11 @@ GLOBAL_LIST_EMPTY(possible_items)
var/payout_bonus = 0
var/area/dropoff = null
+/datum/objective/contract/is_valid_target(datum/mind/possible_target)
+ if(HAS_TRAIT(possible_target, TRAIT_HAS_BEEN_KIDNAPPED))
+ return FALSE
+ return ..()
+
// Generate a random valid area on the station that the dropoff will happen.
/datum/objective/contract/proc/generate_dropoff()
var/found = FALSE
diff --git a/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm b/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm
index 84f3a151dd2..2b323d51162 100644
--- a/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm
+++ b/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm
@@ -271,6 +271,10 @@
. = TRUE
if("send")
start_sending()
+ //We ensure that the holding facility is loaded in time in case we're selling mobs.
+ //This isn't the prettiest place to put it, but 'start_sending()' is also used by civilian bounty computers
+ //And we don't need them to also load the holding facility.
+ SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY)
. = TRUE
if("stop")
stop_sending()
@@ -283,12 +287,9 @@
status_report = "Predicted value: "
var/value = 0
- var/datum/export_report/report = new
+
var/obj/machinery/piratepad/pad = pad_ref?.resolve()
- for(var/atom/movable/AM in get_turf(pad))
- if(AM == pad)
- continue
- export_item_and_contents(AM, apply_elastic = FALSE, dry_run = TRUE, external_report = report, ignore_typecache = nosell_typecache)
+ var/datum/export_report/report = pirate_export_loop(pad)
for(var/datum/export/exported_datum in report.total_amount)
status_report += exported_datum.total_printout(report,notes = FALSE)
@@ -303,13 +304,8 @@
if(!sending)
return
- var/datum/export_report/report = new
var/obj/machinery/piratepad/pad = pad_ref?.resolve()
-
- for(var/atom/movable/item_on_pad in get_turf(pad))
- if(item_on_pad == pad)
- continue
- export_item_and_contents(item_on_pad, apply_elastic = FALSE, delete_unsold = FALSE, external_report = report, ignore_typecache = nosell_typecache)
+ var/datum/export_report/report = pirate_export_loop(pad, dry_run = FALSE)
status_report = "Sold: "
var/value = 0
@@ -341,6 +337,33 @@
pad.icon_state = pad.idle_state
sending = FALSE
+///The loop that calculates the value of stuff on a pirate pad, or plain sell them if dry_run is FALSE.
+/obj/machinery/computer/piratepad_control/proc/pirate_export_loop(obj/machinery/piratepad/pad, dry_run = TRUE)
+ var/datum/export_report/report = new
+ for(var/atom/movable/item_on_pad as anything in get_turf(pad))
+ if(item_on_pad == pad)
+ continue
+ var/list/hidden_mobs = list()
+ var/skip_movable = FALSE
+ var/list/item_contents = item_on_pad.get_all_contents()
+ for(var/atom/movable/thing in reverse_range(item_contents))
+ ///Don't destroy/sell stuff like the captain's laser gun, or borgs.
+ if(thing.resistance_flags & INDESTRUCTIBLE || is_type_in_typecache(thing, nosell_typecache))
+ skip_movable = TRUE
+ break
+ if(isliving(thing))
+ hidden_mobs += thing
+ if(skip_movable)
+ continue
+ for(var/mob/living/hidden as anything in hidden_mobs)
+ ///Sell mobs, but leave their contents intact.
+ export_single_item(hidden, apply_elastic = FALSE, dry_run = dry_run, external_report = report)
+ ///there are still licing mobs inside that item. Stop, don't sell it ffs.
+ if(locate(/mob/living) in item_on_pad.get_all_contents())
+ continue
+ export_item_and_contents(item_on_pad, apply_elastic = FALSE, dry_run = dry_run, delete_unsold = FALSE, external_report = report, ignore_typecache = nosell_typecache)
+ return report
+
/// Prepares to sell the items on the pad
/obj/machinery/computer/piratepad_control/proc/start_sending()
var/obj/machinery/piratepad/pad = pad_ref?.resolve()
@@ -391,7 +414,7 @@
/datum/export/pirate/ransom/get_cost(atom/movable/exported_item)
var/mob/living/carbon/human/ransomee = exported_item
- if(ransomee.stat != CONSCIOUS || !ransomee.mind) //mint condition only
+ if(ransomee.stat != CONSCIOUS || !ransomee.mind || HAS_TRAIT(ransomee.mind, TRAIT_HAS_BEEN_KIDNAPPED)) //mint condition only
return 0
else if(FACTION_PIRATE in ransomee.faction) //can't ransom your fellow pirates to CentCom!
return 0
@@ -400,6 +423,33 @@
else
return 1000
+/datum/export/pirate/ransom/sell_object(mob/living/carbon/human/sold_item, datum/export_report/report, dry_run = TRUE, apply_elastic = TRUE)
+ . = ..()
+ if(. == EXPORT_NOT_SOLD)
+ return
+ var/turf/picked_turf = pick(GLOB.holdingfacility)
+ sold_item.forceMove(picked_turf)
+ var/mob_cost = get_cost(sold_item)
+ sold_item.process_capture(mob_cost, mob_cost * 1.2)
+ do_sparks(8, FALSE, sold_item)
+ playsound(picked_turf, 'sound/weapons/emitter2.ogg', 25, TRUE)
+ sold_item.flash_act()
+ sold_item.adjust_confusion(10 SECONDS)
+ sold_item.adjust_dizzy(10 SECONDS)
+ addtimer(src, CALLBACK(src, PROC_REF(send_back_to_station), sold_item), COME_BACK_FROM_CAPTURE_TIME)
+ to_chat(sold_item, span_hypnophrase("A million voices echo in your head... \"Yaarrr, thanks for the booty, landlubber. \
+ You will be ransomed back to your station, so it's only a matter of time before we ship you back..."))
+
+ return EXPORT_SOLD_DONT_DELETE
+
+///Send them back to the station after a while.
+/datum/export/pirate/ransom/proc/send_back_to_station(mob/living/prisoner)
+ ///Deleted or already bailed out of the place.
+ if(QDELETED(prisoner) || !istype(get_area(prisoner), /area/centcom/central_command_areas/holding))
+ return
+ var/obj/structure/closet/supplypod/back_to_station/return_pod = new()
+ return_pod.return_from_capture(prisoner)
+
/datum/export/pirate/parrot
cost = 2000
unit_name = "alive parrot"
diff --git a/code/modules/antagonists/spy/spy_bounty.dm b/code/modules/antagonists/spy/spy_bounty.dm
index e3c96fc815f..0a9422e07b8 100644
--- a/code/modules/antagonists/spy/spy_bounty.dm
+++ b/code/modules/antagonists/spy/spy_bounty.dm
@@ -133,13 +133,15 @@
do_sparks(3, FALSE, stealing)
// Don't mess with it while it's going away
+ var/had_attack_hand_interaction = stealing.interaction_flags_atom & INTERACT_ATOM_ATTACK_HAND
stealing.interaction_flags_atom &= ~INTERACT_ATOM_ATTACK_HAND
+ var/was_anchored = stealing.anchored
stealing.anchored = TRUE
// Add some pizzazz
- animate(stealing, time = 0.5 SECONDS, transform = matrix(stealing.transform).Scale(0.01), easing = CUBIC_EASING)
+ animate(stealing, time = 0.5 SECONDS, transform = stealing.transform.Scale(0.01), easing = CUBIC_EASING)
if(isitem(stealing) && ((stealing.resistance_flags & INDESTRUCTIBLE) || prob(black_market_prob)))
- addtimer(CALLBACK(src, PROC_REF(send_to_black_market), stealing), 0.5 SECONDS)
+ addtimer(CALLBACK(src, PROC_REF(send_to_black_market), stealing, had_attack_hand_interaction, was_anchored), 0.5 SECONDS)
else
addtimer(CALLBACK(src, PROC_REF(finish_cleanup), stealing), 0.5 SECONDS)
@@ -158,33 +160,31 @@
*
* * thing - The item to put up on the black market.
*/
-/datum/spy_bounty/proc/send_to_black_market(atom/movable/thing)
+/datum/spy_bounty/proc/send_to_black_market(atom/movable/thing, had_attack_hand_interaction, was_anchored)
if(QDELETED(thing)) // Just in case anything does anything weird
return FALSE
- thing.interaction_flags_atom = initial(thing.interaction_flags_atom)
- thing.anchored = initial(thing.anchored)
+ ///reset the appearance and all.
+ if(had_attack_hand_interaction)
+ thing.interaction_flags_atom |= INTERACT_ATOM_ATTACK_HAND
+ thing.anchored = was_anchored
+ thing.transform = thing.transform.Scale(10)
thing.moveToNullspace()
- var/datum/market_item/new_item = new()
- new_item.item = thing
- new_item.name = "Stolen [thing.name]"
- new_item.desc = "A [thing.name], stolen from somewhere on the station. Whoever owned it probably wouldn't be happy to see it here."
- new_item.category = "Fenced Goods"
- new_item.stock = 1
- new_item.availability_prob = 100
-
+ var/item_price
switch(difficulty)
if(SPY_DIFFICULTY_EASY)
- new_item.price = PAYCHECK_COMMAND * 2.5
+ item_price = PAYCHECK_COMMAND * 2.5
if(SPY_DIFFICULTY_MEDIUM)
- new_item.price = PAYCHECK_COMMAND * 5
+ item_price = PAYCHECK_COMMAND * 5
if(SPY_DIFFICULTY_HARD)
- new_item.price = PAYCHECK_COMMAND * 10
+ item_price = PAYCHECK_COMMAND * 10
- new_item.price += rand(0, PAYCHECK_COMMAND * 5)
+ item_price += rand(0, PAYCHECK_COMMAND * 5)
if(thing.resistance_flags & INDESTRUCTIBLE)
- new_item.price *= 2
+ item_price *= 2
+
+ var/datum/market_item/stolen_good/new_item = new(thing, item_price)
return SSblackmarket.markets[/datum/market/blackmarket].add_item(new_item)
diff --git a/code/modules/antagonists/traitor/contractor/syndicate_contract.dm b/code/modules/antagonists/traitor/contractor/syndicate_contract.dm
index f2868bdce30..2c9d45e382d 100644
--- a/code/modules/antagonists/traitor/contractor/syndicate_contract.dm
+++ b/code/modules/antagonists/traitor/contractor/syndicate_contract.dm
@@ -15,6 +15,8 @@
var/wanted_message
///List of everything found on the victim at the time of contracting, used to return their stuff afterwards.
var/list/victim_belongings = list()
+ ///timerid for stuff that handles victim chat messages, effects and returnal
+ var/victim_timerid
/datum/syndicate_contract/New(contract_owner, blacklist, type=CONTRACT_PAYOUT_SMALL)
contract = new(src)
@@ -122,20 +124,8 @@
//we'll start the effects in a few seconds since it takes a moment for the pod to leave.
addtimer(CALLBACK(src, PROC_REF(handle_victim_experience), person_sent), 3 SECONDS)
- // This is slightly delayed because of the sleep calls above to handle the narrative.
- // We don't want to tell the station instantly.
- var/points_to_check
- var/datum/bank_account/cargo_account = SSeconomy.get_dep_account(ACCOUNT_CAR)
- if(cargo_account)
- points_to_check = cargo_account.account_balance
- if(points_to_check >= ransom)
- cargo_account.adjust_money(-ransom)
- else
- cargo_account.adjust_money(-points_to_check)
- priority_announce(
- text = "One of your crew was captured by a rival organisation - we've needed to pay their ransom to bring them back. \
- As is policy we've taken a portion of the station's funds to offset the overall cost.",
- sender_override = "Nanotrasen Asset Protection")
+ var/datum/market_item/hostage/market_item = person_sent.process_capture(ransom * (rand(11, 13)/10))
+ RegisterSignal(market_item, COMSIG_MARKET_ITEM_SPAWNED, PROC_REF(on_victim_shipped))
addtimer(CALLBACK(src, PROC_REF(finish_enter)), 3 SECONDS)
@@ -166,10 +156,10 @@
* level - The current stage of harassement they are facing. This increases by itself, looping until finished.
*/
/datum/syndicate_contract/proc/handle_victim_experience(mob/living/victim, level = VICTIM_EXPERIENCE_START)
- // Ship 'em back - dead or alive, 4 minutes wait.
+ // Ship 'em back - dead or alive
// Even if they weren't the target, we're still treating them the same.
if(!level)
- addtimer(CALLBACK(src, PROC_REF(return_victim), victim), (60 * 10) * 4)
+ victim_timerid = addtimer(CALLBACK(src, PROC_REF(return_victim), victim), COME_BACK_FROM_CAPTURE_TIME, TIMER_STOPPABLE)
if(victim.stat == DEAD)
return
@@ -209,7 +199,7 @@
level++ //move onto the next level.
if(time_until_next)
- addtimer(CALLBACK(src, PROC_REF(handle_victim_experience), victim, level), time_until_next)
+ victim_timerid = addtimer(CALLBACK(src, PROC_REF(handle_victim_experience), victim, level), time_until_next, TIMER_STOPPABLE)
#undef VICTIM_EXPERIENCE_START
#undef VICTIM_EXPERIENCE_FIRST_HIT
@@ -217,34 +207,30 @@
#undef VICTIM_EXPERIENCE_THIRD_HIT
#undef VICTIM_EXPERIENCE_LAST_HIT
-// We're returning the victim
+/// We're returning the victim to the station
/datum/syndicate_contract/proc/return_victim(mob/living/victim)
var/list/possible_drop_loc = list()
-
- for(var/turf/possible_drop in contract.dropoff.contents)
+ for(var/turf/possible_drop in shuffle(contract.dropoff.contents))
if(!isspaceturf(possible_drop) && !isclosedturf(possible_drop))
if(!possible_drop.is_blocked_turf())
possible_drop_loc.Add(possible_drop)
- if(!possible_drop_loc.len)
- to_chat(victim, span_hypnophrase("A million voices echo in your head... \"Seems where you got sent here from won't \
- be able to handle our pod... if we wanted the occupant to survive. Brace yourself, corporate dog.\""))
- for(var/turf/possible_drop in contract.dropoff.contents)
- possible_drop_loc.Add(possible_drop)
- if(iscarbon(victim))
- var/mob/living/carbon/carbon_victim = victim
- if(carbon_victim.can_heartattack())
- carbon_victim.set_heartattack(TRUE)
+ var/turf/destination
+ if(length(possible_drop_loc))
+ destination = pick(possible_drop_loc)
- var/pod_rand_loc = rand(1, possible_drop_loc.len)
- var/obj/structure/closet/supplypod/return_pod = new()
- return_pod.bluespace = TRUE
- return_pod.explosionSize = list(0,0,0,0)
- return_pod.style = STYLE_SYNDICATE
+ var/obj/structure/closet/supplypod/back_to_station/return_pod = new()
+ return_pod.return_from_capture(victim, destination)
+ returnal_side_effects(return_pod, victim)
- do_sparks(8, FALSE, victim)
- victim.visible_message(span_notice("[victim] vanishes..."))
+///Called if the victim is being returned to the station early, when from the black market.
+/datum/syndicate_contract/proc/on_victim_shipped(datum/market_item/source, obj/item/market_uplink/uplink, shipping_method, turf/shipping_loc)
+ SIGNAL_HANDLER
+ deltimer(victim_timerid)
+ returnal_side_effects(shipping_loc, source.item)
+///The annoying negative effects applied to the victim when returned to the station.
+/datum/syndicate_contract/proc/returnal_side_effects(atom/dropoff_location, mob/living/victim)
for(var/datum/weakref/belonging_ref in victim_belongings)
var/obj/item/belonging = belonging_ref.resolve()
if(!belonging)
@@ -256,16 +242,12 @@
continue
if(belonging == human_victim.shoes)
continue
- belonging.forceMove(return_pod)
+ belonging.forceMove(dropoff_location)
- for(var/obj/item/W in victim_belongings)
- W.forceMove(return_pod)
-
- victim.forceMove(return_pod)
+ for(var/obj/item/item in victim_belongings)
+ item.forceMove(dropoff_location)
victim.flash_act()
victim.adjust_eye_blur(3 SECONDS)
victim.adjust_dizzy(3.5 SECONDS)
victim.adjust_confusion(2 SECONDS)
-
- new /obj/effect/pod_landingzone(possible_drop_loc[pod_rand_loc], return_pod)
diff --git a/code/modules/antagonists/traitor/objectives/kidnapping.dm b/code/modules/antagonists/traitor/objectives/kidnapping.dm
index 73a3fc3b3f0..d6aec912fdb 100644
--- a/code/modules/antagonists/traitor/objectives/kidnapping.dm
+++ b/code/modules/antagonists/traitor/objectives/kidnapping.dm
@@ -15,6 +15,8 @@
var/alive_bonus = 0
/// All stripped targets belongings (weakrefs)
var/list/target_belongings = list()
+ /// The ID of the stoppable timer for returning the captured crew
+ var/list/victim_timerid
duplicate_type = /datum/traitor_objective/target_player
@@ -216,6 +218,7 @@
new /obj/effect/pod_landingzone(get_turf(user), new_pod)
/datum/traitor_objective/target_player/kidnapping/proc/enter_check(obj/structure/closet/supplypod/extractionpod/source, entered_atom)
+ SIGNAL_HANDLER
if(!istype(source))
CRASH("Kidnapping objective's enter_check called with source being not an extraction pod: [source ? source.type : "N/A"]")
@@ -224,9 +227,6 @@
var/mob/living/carbon/human/sent_mob = entered_atom
- if(sent_mob.mind)
- ADD_TRAIT(sent_mob.mind, TRAIT_HAS_BEEN_KIDNAPPED, TRAIT_GENERIC)
-
for(var/obj/item/belonging in sent_mob.gather_belongings())
if(belonging == sent_mob.get_item_by_slot(ITEM_SLOT_ICLOTHING) || belonging == sent_mob.get_item_by_slot(ITEM_SLOT_FEET))
continue
@@ -236,12 +236,8 @@
continue
target_belongings.Add(WEAKREF(belonging))
- var/datum/bank_account/cargo_account = SSeconomy.get_dep_account(ACCOUNT_CAR)
-
- if(cargo_account) //Just in case
- cargo_account.adjust_money(-min(rand(1000, 3000), cargo_account.account_balance)) //Not so much, especially for competent cargo. Plus this can't be mass-triggered like it has been done with contractors
-
- priority_announce("One of your crew was captured by a rival organisation - we've needed to pay their ransom to bring them back. As is policy we've taken a portion of the station's funds to offset the overall cost.", "Nanotrasen Asset Protection", has_important_message = TRUE)
+ var/datum/market_item/hostage/market_item = sent_mob.process_capture(rand(1000, 3000))
+ RegisterSignal(market_item, COMSIG_MARKET_ITEM_SPAWNED, PROC_REF(on_victim_shipped))
addtimer(CALLBACK(src, PROC_REF(handle_target), sent_mob), 1.5 SECONDS)
@@ -257,7 +253,7 @@
source.startExitSequence(source)
/datum/traitor_objective/target_player/kidnapping/proc/handle_target(mob/living/carbon/human/sent_mob)
- addtimer(CALLBACK(src, PROC_REF(return_target), sent_mob), 3 MINUTES)
+ victim_timerid = addtimer(CALLBACK(src, PROC_REF(return_target), sent_mob), COME_BACK_FROM_CAPTURE_TIME, TIMER_STOPPABLE)
if(sent_mob.stat == DEAD)
return
@@ -266,29 +262,24 @@
sent_mob.adjust_dizzy(10 SECONDS)
sent_mob.set_eye_blur_if_lower(100 SECONDS)
sent_mob.dna.species.give_important_for_life(sent_mob) // so plasmamen do not get left for dead
- to_chat(sent_mob, span_hypnophrase(span_reallybig("A million voices echo in your head... \"Your mind held many valuable secrets - \
+ to_chat(sent_mob, span_hypnophrase("A million voices echo in your head... \"Your mind held many valuable secrets - \
we thank you for providing them. Your value is expended, and you will be ransomed back to your station. We always get paid, \
- so it's only a matter of time before we ship you back...\"")))
+ so it's only a matter of time before we ship you back...\""))
/datum/traitor_objective/target_player/kidnapping/proc/return_target(mob/living/carbon/human/sent_mob)
if(!sent_mob || QDELETED(sent_mob)) //suicided and qdeleted themselves
return
- var/turf/return_turf = get_safe_random_station_turf()
- if(!return_turf) //SOMEHOW
- to_chat(sent_mob, span_hypnophrase(span_reallybig("A million voices echo in your head... \"Seems where you got sent here from won't \
- be able to handle our pod... You will die here instead.\"")))
- if (sent_mob.can_heartattack())
- sent_mob.set_heartattack(TRUE)
- return
+ var/obj/structure/closet/supplypod/back_to_station/return_pod = new()
+ return_pod.return_from_capture(sent_mob)
+ returnal_side_effects(return_pod, sent_mob)
- var/obj/structure/closet/supplypod/return_pod = new()
- return_pod.bluespace = TRUE
- return_pod.explosionSize = list(0,0,0,0)
- return_pod.style = STYLE_SYNDICATE
+/datum/traitor_objective/target_player/kidnapping/proc/on_victim_shipped(datum/market_item/source, obj/item/market_uplink/uplink, shipping_method, turf/shipping_loc)
+ SIGNAL_HANDLER
+ deltimer(victim_timerid)
+ returnal_side_effects(shipping_loc, source.item)
- do_sparks(8, FALSE, sent_mob)
- sent_mob.visible_message(span_notice("[sent_mob] vanishes!"))
+/datum/traitor_objective/target_player/kidnapping/proc/returnal_side_effects(atom/dropoff_location, mob/living/carbon/human/sent_mob)
for(var/obj/item/belonging in sent_mob.gather_belongings())
if(belonging == sent_mob.get_item_by_slot(ITEM_SLOT_ICLOTHING) || belonging == sent_mob.get_item_by_slot(ITEM_SLOT_FEET))
continue
@@ -298,13 +289,10 @@
var/obj/item/belonging = belonging_ref.resolve()
if(!belonging)
continue
- belonging.forceMove(return_pod)
+ belonging.forceMove(dropoff_location)
- sent_mob.forceMove(return_pod)
sent_mob.flash_act()
sent_mob.adjust_confusion(10 SECONDS)
sent_mob.adjust_dizzy(10 SECONDS)
sent_mob.set_eye_blur_if_lower(100 SECONDS)
sent_mob.dna.species.give_important_for_life(sent_mob) // so plasmamen do not get left for dead
-
- new /obj/effect/pod_landingzone(return_turf, return_pod)
diff --git a/code/modules/cargo/exports.dm b/code/modules/cargo/exports.dm
index fd6e91a8580..224a27062f5 100644
--- a/code/modules/cargo/exports.dm
+++ b/code/modules/cargo/exports.dm
@@ -30,6 +30,14 @@ Then the player gets the profit from selling his own wasted time.
///set to false if any objects in a dry run were unscannable
var/all_contents_scannable = TRUE
+/// Makes sure the exports list is populated and that the report isn't null.
+/proc/init_export(datum/export_report/external_report)
+ if(!length(GLOB.exports_list))
+ setupExports()
+ if(isnull(external_report))
+ external_report = new
+ return external_report
+
/*
* Handles exporting a movable atom and its contents
* Arguments:
@@ -40,32 +48,15 @@ Then the player gets the profit from selling his own wasted time.
** ignore_typecache: typecache containing types that should be completely ignored
*/
/proc/export_item_and_contents(atom/movable/exported_atom, apply_elastic = TRUE, delete_unsold = TRUE, dry_run = FALSE, datum/export_report/external_report, list/ignore_typecache)
- if(!GLOB.exports_list.len)
- setupExports()
+ external_report = init_export()
var/list/contents = exported_atom.get_all_contents_ignoring(ignore_typecache)
- var/datum/export_report/report = external_report
-
- if(!report) //If we don't have any longer transaction going on
- report = new
-
// We go backwards, so it'll be innermost objects sold first. We also make sure nothing is accidentally delete before everything is sold.
var/list/to_delete = list()
for(var/atom/movable/thing as anything in reverse_range(contents))
- var/sold = FALSE
- for(var/datum/export/export as anything in GLOB.exports_list)
- if(export.applies_to(thing, apply_elastic))
- if(!dry_run && (SEND_SIGNAL(thing, COMSIG_ITEM_PRE_EXPORT) & COMPONENT_STOP_EXPORT))
- break
- //Don't add value of unscannable items for a dry run report
- if(dry_run && !export.scannable)
- report.all_contents_scannable = FALSE
- break
- sold = export.sell_object(thing, report, dry_run, apply_elastic)
- report.exported_atoms += " [thing.name]"
- break
- if(!dry_run && (sold || delete_unsold))
+ var/sold = _export_loop(thing, apply_elastic, dry_run, external_report)
+ if(!dry_run && (sold || delete_unsold) && sold != EXPORT_SOLD_DONT_DELETE)
if(ismob(thing))
thing.investigate_log("deleted through cargo export", INVESTIGATE_CARGO)
to_delete += thing
@@ -74,7 +65,35 @@ Then the player gets the profit from selling his own wasted time.
if(!QDELETED(thing))
qdel(thing)
- return report
+ return external_report
+
+/// It works like export_item_and_contents(), however it ignores the contents. Meaning only `exported_atom` will be valued.
+/proc/export_single_item(atom/movable/exported_atom, apply_elastic = TRUE, delete_unsold = TRUE, dry_run = FALSE, datum/export_report/external_report)
+ external_report = init_export()
+
+ var/sold = _export_loop(exported_atom, apply_elastic, dry_run, external_report)
+ if(!dry_run && (sold || delete_unsold) && sold != EXPORT_SOLD_DONT_DELETE)
+ if(ismob(exported_atom))
+ exported_atom.investigate_log("deleted through cargo export", INVESTIGATE_CARGO)
+ qdel(exported_atom)
+
+ return external_report
+
+/// The main bit responsible for selling the item. Shared by export_single_item() and export_item_and_contents()
+/proc/_export_loop(atom/movable/exported_atom, apply_elastic = TRUE, dry_run = FALSE, datum/export_report/external_report)
+ var/sold = EXPORT_NOT_SOLD
+ for(var/datum/export/export as anything in GLOB.exports_list)
+ if(export.applies_to(exported_atom, apply_elastic))
+ if(!dry_run && (SEND_SIGNAL(exported_atom, COMSIG_ITEM_PRE_EXPORT) & COMPONENT_STOP_EXPORT))
+ break
+ //Don't add value of unscannable items for a dry run report
+ if(dry_run && !export.scannable)
+ external_report.all_contents_scannable = FALSE
+ break
+ sold = export.sell_object(exported_atom, exported_atom, dry_run, apply_elastic)
+ external_report.exported_atoms += " [exported_atom.name]"
+ break
+ return sold
/datum/export
/// Unit name. Only used in "Received [total_amount] [name]s [message]."
@@ -164,7 +183,7 @@ Then the player gets the profit from selling his own wasted time.
var/export_amount = get_amount(sold_item)
if(export_amount <= 0 || (export_value <= 0 && !allow_negative_cost))
- return FALSE
+ return EXPORT_NOT_SOLD
// If we're not doing a dry run, send COMSIG_ITEM_EXPORTED to the sold item
var/export_result
@@ -180,7 +199,7 @@ Then the player gets the profit from selling his own wasted time.
if(apply_elastic)
cost *= NUM_E**(-1 * k_elasticity * export_amount) //marginal cost modifier
SSblackbox.record_feedback("nested tally", "export_sold_cost", 1, list("[sold_item.type]", "[export_value]"))
- return TRUE
+ return EXPORT_SOLD
/*
* Total printout for the cargo console.
diff --git a/code/modules/cargo/markets/_market.dm b/code/modules/cargo/markets/_market.dm
index a4af2bc981d..78585ed842f 100644
--- a/code/modules/cargo/markets/_market.dm
+++ b/code/modules/cargo/markets/_market.dm
@@ -20,39 +20,53 @@
categories += item.category
available_items[item.category] = list()
- available_items[item.category] += item
+ available_items[item.category][item.identifier] = item
+ RegisterSignal(item, COMSIG_QDELETING, PROC_REF(on_item_del))
return TRUE
+/datum/market/proc/on_item_del(datum/market_item/item)
+ SIGNAL_HANDLER
+ available_items[item.category] -= item.identifier
+ if(!length(available_items[item.category]))
+ available_items -= item.category
+
/// Handles buying the item, this is mainly for future use and moving the code away from the uplink.
-/datum/market/proc/purchase(item, category, method, obj/item/market_uplink/uplink, user)
- if(!istype(uplink) || !(method in shipping))
+/datum/market/proc/purchase(identifier, category, method, obj/item/market_uplink/uplink, user)
+ var/datum/market_item/item = available_items[category][identifier]
+ if(isnull(item))
return FALSE
- for(var/datum/market_item/I in available_items[category])
- if(I.type != item)
- continue
- var/price = I.price + shipping[method]
-
- if(!uplink.current_user)///There is no ID card on the user, or the ID card has no account
- to_chat(user, span_warning("The uplink sparks, as it can't identify an ID card with a bank account on you."))
- return FALSE
- var/balance = uplink?.current_user.account_balance
-
- // I can't get the price of the item and shipping in a clean way to the UI, so I have to do this.
- if(balance < price)
- to_chat(user, span_warning("You don't have enough credits in [uplink] for [I] with [method] shipping."))
- return FALSE
-
- if(I.buy(uplink, user, method))
- uplink.current_user.adjust_money(-price, "Other: Third Party Transaction")
- if(ismob(user))
- var/mob/m_user = user
- m_user.playsound_local(get_turf(m_user), 'sound/machines/twobeep_high.ogg', 50, TRUE)
- return TRUE
+ if(!istype(uplink) || !((method in shipping) || (method in item.shipping_override)))
return FALSE
+ var/shipment_fee = item.shipping_override?[method]
+ if(isnull(shipment_fee))
+ shipment_fee = shipping[method]
+ var/price = item.price + shipment_fee
+
+ if(!uplink.current_user)///There is no ID card on the user, or the ID card has no account
+ to_chat(user, span_warning("The uplink sparks, as it can't identify an ID card with a bank account on you."))
+ return FALSE
+ var/balance = uplink?.current_user.account_balance
+
+ // I can't get the price of the item and shipping in a clean way to the UI, so I have to do this.
+ if(balance < price)
+ to_chat(user, span_warning("You don't have enough credits in [uplink] for [item] with [method] shipping."))
+ return FALSE
+
+ if(item.buy(uplink, user, method))
+ uplink.current_user.adjust_money(-price, "Other: Third Party Transaction")
+ if(ismob(user))
+ var/mob/m_user = user
+ m_user.playsound_local(get_turf(m_user), 'sound/machines/twobeep_high.ogg', 50, TRUE)
+ return TRUE
+
+ return FALSE
+
/datum/market/blackmarket
name = "Black Market"
- shipping = list(SHIPPING_METHOD_LTSRBT =50,
- SHIPPING_METHOD_LAUNCH =10,
- SHIPPING_METHOD_TELEPORT=75)
+ shipping = list(
+ SHIPPING_METHOD_LTSRBT = 40,
+ SHIPPING_METHOD_LAUNCH = 10,
+ SHIPPING_METHOD_TELEPORT= 75,
+ )
diff --git a/code/modules/cargo/markets/market_item.dm b/code/modules/cargo/markets/market_item.dm
index d5689c17a45..01eb37c077b 100644
--- a/code/modules/cargo/markets/market_item.dm
+++ b/code/modules/cargo/markets/market_item.dm
@@ -14,7 +14,7 @@
var/stock
/// Path to or the item itself what this entry is for, this should be set even if you override spawn_item to spawn your item.
- var/obj/item/item
+ var/atom/movable/item
/// Minimum price for the item if generated randomly.
var/price_min = 0
@@ -27,27 +27,60 @@
/// Probability for this item to be available. Used by SSblackmarket on init.
var/availability_prob = 0
+ ///The identifier for the market item, generated on runtime and used to access them in the market categories.
+ var/identifier
+
+ ///If set, these will override the shipment methods set by the market
+ var/list/shipping_override
+
/datum/market_item/New()
if(isnull(price))
price = rand(price_min, price_max)
if(isnull(stock))
stock = rand(stock_min, stock_max)
+ identifier = "[type]"
+
+///For 'dynamic' market items generated on runtime, this proc is to be used to properly sets the item, especially if it's a hardref.
+/datum/market_item/proc/set_item(path_or_ref)
+ //we're replacing the item to sell, and the old item is an instance!
+ if(ismovable(item))
+ UnregisterSignal(item, COMSIG_QDELETING)
+ item = path_or_ref
+ identifier = "[path_or_ref]"
+ if(ismovable(path_or_ref))
+ RegisterSignal(item, COMSIG_QDELETING, PROC_REF(on_item_del))
+ identifier = "[REF(src)]"
/datum/market_item/Destroy()
item = null
return ..()
+/datum/market_item/proc/on_item_del(datum/source)
+ SIGNAL_HANDLER
+ qdel(src)
+
/// Used for spawning the wanted item, override if you need to do something special with the item.
-/datum/market_item/proc/spawn_item(loc)
+/datum/market_item/proc/spawn_item(loc, datum/market_purchase/purchase)
+ SHOULD_CALL_PARENT(TRUE)
+ SEND_SIGNAL(src, COMSIG_MARKET_ITEM_SPAWNED, purchase.uplink, purchase.method, loc)
if(ismovable(item))
- item.forceMove(loc)
- return item
+ var/atom/movable/return_item = item
+ UnregisterSignal(item, COMSIG_QDELETING)
+ if(isnull(loc))
+ item.moveToNullspace()
+ else
+ do_sparks(8, FALSE, item)
+ item.visible_message(span_notice("[item] vanishes..."))
+ item.forceMove(loc)
+ item = null
+ return return_item
if(ispath(item))
return new item(loc)
CRASH("Invalid item type for market item [item || "null"]")
/// Buys the item and makes SSblackmarket handle it.
/datum/market_item/proc/buy(obj/item/market_uplink/uplink, mob/buyer, shipping_method)
+ SHOULD_CALL_PARENT(TRUE)
// Sanity
if(!istype(uplink) || !istype(buyer))
return FALSE
@@ -70,16 +103,34 @@
/datum/market_purchase
/// The entry being purchased.
var/datum/market_item/entry
- /// Instance of the item being sent.
- var/item
+ /// Instance of the item being sent, used by the market telepad
+ var/atom/movable/item
/// The uplink where this purchase was done from.
var/obj/item/market_uplink/uplink
/// Shipping method used to buy this item.
var/method
-/datum/market_purchase/New(_entry, _uplink, _method)
- entry = _entry
- if(!ispath(entry.item))
+/datum/market_purchase/New(datum/market_item/entry, obj/item/market_uplink/uplink, method)
+ if(!uplink || !entry || !method)
+ CRASH("[type] created with a false value arg: (entry: [entry] - uplink: [uplink] - method: [method])")
+ src.entry = entry
+ src.uplink = uplink
+ src.method = method
+ RegisterSignal(entry, COMSIG_QDELETING, PROC_REF(on_instance_del))
+ RegisterSignal(uplink, COMSIG_QDELETING, PROC_REF(on_instance_del))
+ if(ismovable(entry.item))
item = entry.item
- uplink = _uplink
- method = _method
+ RegisterSignal(entry.item, COMSIG_QDELETING, PROC_REF(on_instance_del))
+
+/datum/market_purchase/Destroy()
+ entry = null
+ uplink = null
+ SSblackmarket.queued_purchases -= src
+ return ..()
+
+/datum/market_purchase/proc/on_instance_del(datum/source)
+ SIGNAL_HANDLER
+ if(QDELETED(src))
+ return
+ // Uh oh, uplink or item is gone. We will just keep the money and you will not get your order.
+ qdel(src)
diff --git a/code/modules/cargo/markets/market_items/consumables.dm b/code/modules/cargo/markets/market_items/consumables.dm
index 153241e5d3d..5550d31c5f8 100644
--- a/code/modules/cargo/markets/market_items/consumables.dm
+++ b/code/modules/cargo/markets/market_items/consumables.dm
@@ -39,7 +39,8 @@
/obj/item/storage/pill_bottle/lsd,
/obj/item/storage/pill_bottle/aranesp,
/obj/item/storage/pill_bottle/stimulant))
- return new pillbottle(loc)
+ item = pillbottle
+ return ..()
/datum/market_item/consumable/floor_pill
name = "Strange Pill"
diff --git a/code/modules/cargo/markets/market_items/hostages.dm b/code/modules/cargo/markets/market_items/hostages.dm
new file mode 100644
index 00000000000..6551ee6156b
--- /dev/null
+++ b/code/modules/cargo/markets/market_items/hostages.dm
@@ -0,0 +1,84 @@
+///A special category for mobs captured by pirates, tots and contractors, should someone ever want to get them back in advance.
+/datum/market_item/hostage
+ category = "Hostages"
+ stock = 1
+ availability_prob = 100
+ shipping_override = list(SHIPPING_METHOD_LTSRBT = 0, SHIPPING_METHOD_SUPPLYPOD = 350)
+ /// temporary reference to the 4 in 7 chances of signaler and electropack.
+ var/obj/item/assembly/signaler/signaler
+
+/datum/market_item/hostage/New(mob/living/mob, new_price)
+ ..()
+ set_item(mob)
+ name = "[mob.real_name]"
+ var/specimen = initial(mob.name)
+ var/humie_mob = ishuman(mob)
+ if(humie_mob)
+ var/mob/living/carbon/human/humie = mob
+ specimen = humie.dna.species.name
+ desc = pick(list(
+ "If you're looking for a recently stolen [specimen], you've come to the right place.",
+ "we've recently aquired a fine [specimen] from a station around here, eheh...",
+ "For a limited time, we're offering this [specimen] for you to buy (back).",
+ ))
+ desc += " DISCLAIMER: The offer will expire once the creature is returned to the station."
+ if(humie_mob)
+ desc += "[mob.p_they(TRUE)] may be delivered handcuffed, for safety of course."
+
+ price = new_price
+ RegisterSignal(mob, COMSIG_LIVING_RETURN_FROM_CAPTURE, PROC_REF(on_return_from_capture))
+
+/datum/market_item/hostage/proc/on_return_from_capture(mob/living/source, turf/destination)
+ SIGNAL_HANDLER
+ qdel(src) //as if we never existed, our mentions we'll be removed from the market.
+
+/datum/market_item/hostage/Destroy()
+ signaler = null
+ return ..()
+
+/datum/market_item/hostage/buy(obj/item/market_uplink/uplink, mob/buyer, shipping_method)
+ . = ..()
+ var/mob/living/humie = item
+ if(!. || !istype(humie) || !prob(57)) // 3 in 7 chance of the electropack set not spawning...
+ return
+ signaler = new(uplink.drop_location())
+ RegisterSignal(signaler, COMSIG_QDELETING, PROC_REF(clear_signaler_ref))
+ signaler.set_frequency(sanitize_frequency(rand(MIN_FREE_FREQ, MAX_FREE_FREQ)))
+ signaler.code = rand(1, 100)
+ buyer.put_in_hands(signaler)
+ to_chat(buyer, span_notice("A [signaler] appears [buyer.is_holding(signaler) ? "in your hands" : "at your feet"]!"))
+
+/datum/market_item/hostage/proc/clear_signaler_ref(datum/source)
+ SIGNAL_HANDLER
+ signaler = null
+
+/datum/market_item/hostage/spawn_item(loc, datum/market_purchase/purchase)
+ var/mob/living/mob = item
+ UnregisterSignal(mob, COMSIG_LIVING_RETURN_FROM_CAPTURE)
+ if(!mob.IsUnconscious())
+ to_chat(mob, span_boldnicegreen("You have been bought back to the station. Be grateful to whoever got you out of the holding facility early."))
+ if(!ishuman(item))
+ return ..()
+ var/mob/living/carbon/human/humie = item
+ if(signaler) //57% chance that the mob is equipped with electropack and cuffs
+ humie.equip_to_slot_or_del(new /obj/item/restraints/handcuffs, ITEM_SLOT_HANDCUFFED, indirect_action = TRUE)
+ if(humie.back) //try to remove the neck item if possible before we attempt to install the collar bomb
+ humie.transferItemToLoc(humie.back, loc)
+ var/obj/item/electropack/pack = new(loc)
+ pack.set_frequency(signaler.frequency)
+ pack.code = signaler.code
+ humie.equip_to_slot_if_possible(pack, ITEM_SLOT_BACK, disable_warning = TRUE)
+ UnregisterSignal(signaler, COMSIG_QDELETING)
+ signaler = null
+ else if(prob(66)) // 29% chance of just cuffs
+ humie.equip_to_slot_or_del(new /obj/item/restraints/handcuffs, ITEM_SLOT_HANDCUFFED, indirect_action = TRUE)
+ else // 14% chance of just a tee souvenir and pin, no cuffs and shit.
+ var/obj/item/clothing/under/misc/syndicate_souvenir/souvenir = new(loc)
+ humie.equip_to_slot_if_possible(souvenir, ITEM_SLOT_ICLOTHING, indirect_action = TRUE)
+ var/obj/item/clothing/accessory/anti_sec_pin/pin = new(loc)
+ pin.attach(souvenir)
+
+ if(isnull(humie.w_uniform))
+ //FUCKING SLAVES, GET YOUR CLOTHES BACK ON!
+ humie.equip_to_slot_or_del(new /obj/item/clothing/under/costume/jabroni(humie), ITEM_SLOT_ICLOTHING, indirect_action = TRUE)
+ return ..()
diff --git a/code/modules/cargo/markets/market_items/misc.dm b/code/modules/cargo/markets/market_items/misc.dm
index 0953a73e0ac..de0fcaa9256 100644
--- a/code/modules/cargo/markets/market_items/misc.dm
+++ b/code/modules/cargo/markets/market_items/misc.dm
@@ -68,9 +68,11 @@
stock_max = 3
availability_prob = 40
-/datum/market_item/misc/holywater/spawn_item(loc)
+/datum/market_item/misc/holywater/spawn_item(loc, datum/market_purchase/purchase)
if (prob(6.66))
- return new /obj/item/reagent_containers/cup/beaker/unholywater(loc)
+ item = /obj/item/reagent_containers/cup/beaker/unholywater
+ else
+ item = initial(item)
return ..()
/datum/market_item/misc/strange_seed
diff --git a/code/modules/cargo/markets/market_items/stolen_goods.dm b/code/modules/cargo/markets/market_items/stolen_goods.dm
new file mode 100644
index 00000000000..c9c17f1d2b6
--- /dev/null
+++ b/code/modules/cargo/markets/market_items/stolen_goods.dm
@@ -0,0 +1,12 @@
+///A special category for goods stolen by spies for their bounties.
+/datum/market_item/stolen_good
+ category = "Fenced Goods"
+ stock = 1
+ availability_prob = 100
+
+/datum/market_item/stolen_good/New(atom/movable/thing, thing_price)
+ ..()
+ set_item(thing)
+ name = "Stolen [thing.name]"
+ desc = "A [thing.name], stolen from somewhere on the station. Whoever owned it probably wouldn't be happy to see it here."
+ price = thing_price
diff --git a/code/modules/cargo/markets/market_items/tools.dm b/code/modules/cargo/markets/market_items/tools.dm
index c20834e640b..5d036fae0ef 100644
--- a/code/modules/cargo/markets/market_items/tools.dm
+++ b/code/modules/cargo/markets/market_items/tools.dm
@@ -1,6 +1,18 @@
/datum/market_item/tool
category = "Tools"
+/datum/market_item/tool/blackmarket_telepad
+ name = "Black Market LTSRBT"
+ desc = "Need a faster and better way of transporting your illegal goods from and to the \
+ station? Fear not, the Long-To-Short-Range-Bluespace-Transceiver is here to help. \
+ Contains a LTSRBT circuit. Bluespace crystals and ansible not included."
+ item = /obj/item/circuitboard/machine/ltsrbt
+ stock_min = 2
+ stock_max = 4
+ price_min = CARGO_CRATE_VALUE * 2.5
+ price_max = CARGO_CRATE_VALUE * 3.75
+ availability_prob = 100
+
/datum/market_item/tool/caravan_wrench
name = "Experimental Wrench"
desc = "The extra fast and handy wrench you always wanted!"
diff --git a/code/modules/cargo/markets/market_telepad.dm b/code/modules/cargo/markets/market_telepad.dm
index e99e4b88d22..7a3365d7a0f 100644
--- a/code/modules/cargo/markets/market_telepad.dm
+++ b/code/modules/cargo/markets/market_telepad.dm
@@ -26,13 +26,13 @@
/// The time it takes for the machine to recharge before being able to send or receive items.
var/recharge_time = 0
/// Current recharge progress.
- var/recharge_cooldown = 0
+ COOLDOWN_DECLARE(recharge_cooldown)
/// Base recharge time in seconds which is used to get recharge_time.
var/base_recharge_time = 100
/// Current /datum/market_purchase being received.
- var/receiving
+ var/datum/market_purchase/receiving
/// Current /datum/market_purchase being sent to the target uplink.
- var/transmitting
+ var/datum/market_purchase/transmitting
/// Queue for purchases that the machine should receive and send.
var/list/datum/market_purchase/queue = list()
@@ -67,46 +67,48 @@
/obj/machinery/ltsrbt/proc/add_to_queue(datum/market_purchase/purchase)
if(!recharge_cooldown && !receiving && !transmitting)
receiving = purchase
- return
- queue += purchase
+ else
+ queue += purchase
+ RegisterSignal(receiving, COMSIG_QDELETING, PROC_REF(on_receiving_del))
+
+/obj/machinery/ltsrbt/proc/on_receiving_del(datum/market_purchase/purchase)
+ SIGNAL_HANDLER
+ queue -= purchase
+ if(receiving == purchase)
+ receiving = null
+ if(transmitting == purchase)
+ transmitting = null
/obj/machinery/ltsrbt/process(seconds_per_tick)
if(machine_stat & NOPOWER)
return
- if(recharge_cooldown > 0)
- recharge_cooldown -= seconds_per_tick
+ if(!COOLDOWN_FINISHED(src, recharge_cooldown) && isnull(receiving) && isnull(transmitting))
return
- var/turf/T = get_turf(src)
- if(receiving)
- var/datum/market_purchase/P = receiving
+ COOLDOWN_START(src, recharge_cooldown, recharge_time)
- P.item = P.entry.spawn_item(T)
+ var/turf/turf = get_turf(src)
+ if(receiving)
+
+ receiving.item = receiving.entry.spawn_item(turf, receiving)
use_power(power_usage_per_teleport / power_efficiency)
var/datum/effect_system/spark_spread/sparks = new
sparks.set_up(5, 1, get_turf(src))
- sparks.attach(P.item)
+ sparks.attach(receiving.item)
sparks.start()
+ transmitting = receiving
receiving = null
- transmitting = P
recharge_cooldown = recharge_time
return
- else if(transmitting)
- var/datum/market_purchase/P = transmitting
- if(!P.item)
- QDEL_NULL(transmitting)
- if(!(P.item in T.contents))
- QDEL_NULL(transmitting)
- return
- do_teleport(P.item, get_turf(P.uplink))
- use_power(power_usage_per_teleport / power_efficiency)
+ if(transmitting)
+ if(transmitting.item.loc == turf)
+ do_teleport(transmitting.item, get_turf(transmitting.uplink))
+ use_power(power_usage_per_teleport / power_efficiency)
QDEL_NULL(transmitting)
-
- recharge_cooldown = recharge_time
return
if(queue.len)
diff --git a/code/modules/cargo/markets/market_uplink.dm b/code/modules/cargo/markets/market_uplink.dm
index a82218082e9..da86161e46a 100644
--- a/code/modules/cargo/markets/market_uplink.dm
+++ b/code/modules/cargo/markets/market_uplink.dm
@@ -9,7 +9,7 @@
var/viewing_category
/// What market is currently being bought from by the uplink?
var/viewing_market
- /// What item is the current uplink attempting to buy?
+ /// the identifier of the item that the current uplink is attempting to buy
var/selected_item
/// Is the uplink in the process of buying the selected item?
var/buying
@@ -56,25 +56,31 @@
current_user = null
data["categories"] = market ? market.categories : null
data["delivery_methods"] = list()
- if(market)
- for(var/delivery in market.shipping)
- data["delivery_methods"] += list(list("name" = delivery, "price" = market.shipping[delivery]))
data["money"] = "N/A cr"
if(current_user)
data["money"] = current_user.account_balance
data["buying"] = buying
+ if(buying && market)
+ var/datum/market_item/target_item = market.available_items[viewing_category][selected_item]
+ var/list/shipping_list = market.shipping
+ if(length(target_item?.shipping_override))
+ shipping_list = target_item.shipping_override
+ for(var/delivery in shipping_list)
+ UNTYPED_LIST_ADD(data["delivery_methods"], list("name" = delivery, "price" = shipping_list[delivery]))
data["items"] = list()
- data["viewing_category"] = viewing_category
+ data["viewing_category"] = market.categories[viewing_category] ? viewing_category : null
data["viewing_market"] = viewing_market
if(viewing_category && market)
if(market.available_items[viewing_category])
- for(var/datum/market_item/I in market.available_items[viewing_category])
+ var/list/market_category = market.available_items[viewing_category]
+ for(var/id in market_category)
+ var/datum/market_item/item = market_category[id]
data["items"] += list(list(
- "id" = I.type,
- "name" = I.name,
- "cost" = I.price,
- "amount" = I.stock,
- "desc" = I.desc || I.name
+ "id" = id,
+ "name" = item.name,
+ "cost" = item.price,
+ "amount" = item.stock,
+ "desc" = item.desc || item.name
))
return data
@@ -123,8 +129,7 @@
if("select")
if(isnull(params["item"]))
return
- var/item = text2path(params["item"])
- selected_item = item
+ selected_item = params["item"]
buying = TRUE
. = TRUE
if("cancel")
diff --git a/code/modules/cargo/packs/imports.dm b/code/modules/cargo/packs/imports.dm
index 223fa6ea85d..20fdf57678a 100644
--- a/code/modules/cargo/packs/imports.dm
+++ b/code/modules/cargo/packs/imports.dm
@@ -118,19 +118,6 @@
if(prob(10)) //A little extra sugar every now and then to shake things up.
new /obj/item/switchblade(our_crate)
-/datum/supply_pack/imports/blackmarket_telepad
- name = "Black Market LTSRBT"
- desc = "Need a faster and better way of transporting your illegal goods from and to the \
- station? Fear not, the Long-To-Short-Range-Bluespace-Transceiver (LTSRBT for short) \
- is here to help. Contains a LTSRBT circuit, two bluespace crystals, and one ansible."
- cost = CARGO_CRATE_VALUE * 20
- contraband = TRUE
- contains = list(
- /obj/item/circuitboard/machine/ltsrbt,
- /obj/item/stack/ore/bluespace_crystal/artificial = 2,
- /obj/item/stock_parts/subspace/ansible,
- )
-
/datum/supply_pack/imports/contraband
name = "'Contraband' Crate"
desc = "Psst.. bud... want some contraband? I can get you a poster, some nice cigs, dank, even some \
diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm
index c9faddeb749..c59f0f31eea 100644
--- a/code/modules/cargo/supplypod.dm
+++ b/code/modules/cargo/supplypod.dm
@@ -241,6 +241,24 @@
/obj/structure/closet/supplypod/open(mob/living/user, force = FALSE, special_effects = TRUE)
return
+///Called by the drop pods that return captured crewmembers from the ninja den.
+/obj/structure/closet/supplypod/proc/return_from_capture(mob/living/victim, turf/destination = get_safe_random_station_turf())
+ if(isnull(destination)) //Uuuuh, something went wrong. This is gonna hurt.
+ to_chat(victim, span_hypnophrase("A million voices echo in your head... \"Seems where you got sent won't \
+ be able to handle our pod... as if we wanted the occupant to survive. Brace yourself, corporate dog.\""))
+ flags_1 &= ~PREVENT_CONTENTS_EXPLOSION_1
+ explosionSize = list(0,1,1,1)
+ destination = get_random_station_turf()
+
+ do_sparks(8, FALSE, victim)
+ victim.visible_message(span_notice("[victim] vanishes..."))
+
+ victim.forceMove(src)
+
+ new /obj/effect/pod_landingzone(destination, src)
+
+ SEND_SIGNAL(victim, COMSIG_LIVING_RETURN_FROM_CAPTURE, destination)
+
/obj/structure/closet/supplypod/proc/handleReturnAfterDeparting(atom/movable/holder = src)
reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open_pod() )
bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever
diff --git a/code/modules/clothing/under/accessories/badges.dm b/code/modules/clothing/under/accessories/badges.dm
index eba2dd29ce8..044647b0b00 100644
--- a/code/modules/clothing/under/accessories/badges.dm
+++ b/code/modules/clothing/under/accessories/badges.dm
@@ -218,8 +218,8 @@ GLOBAL_LIST_INIT(pride_pin_reskins, list(
/obj/item/clothing/accessory/anti_sec_pin/attach(obj/item/clothing/under/attach_to, mob/living/attacher)
. = ..()
- if (!.)
- return FALSE
+ if (!. || isnull(attacher))
+ return
var/target = ishuman(attach_to.loc) ? attach_to.loc : attach_to
log_combat(attacher, target, "pinned an 'arrest me immediately' pin onto", src)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index c652bb9bcd9..fe4403f78c8 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -2615,6 +2615,26 @@ GLOBAL_LIST_EMPTY(fire_appearances)
SEND_SIGNAL(src, COMSIG_LIVING_UNFRIENDED, old_friend)
return TRUE
+/**
+ * Common proc used to deduct money from cargo, announce the kidnapping and add src to the black market.
+ * Returns the black market item, for extra stuff like signals that need to be registered.
+ */
+/mob/living/proc/process_capture(ransom_price, black_market_price)
+ if(ransom_price > 0)
+ var/datum/bank_account/cargo_account = SSeconomy.get_dep_account(ACCOUNT_CAR)
+
+ if(cargo_account) //Just in case
+ cargo_account.adjust_money(-min(ransom_price, cargo_account.account_balance)) //Not so much, especially for competent cargo. Plus this can't be mass-triggered like it has been done with contractors
+ priority_announce("One of your crew was captured by a rival organisation - we've needed to pay their ransom to bring them back. As is policy we've taken a portion of the station's funds to offset the overall cost.", "Nanotrasen Asset Protection", has_important_message = TRUE)
+
+ ///The price should be high enough that the contractor can't just buy 'em back with their cut alone.
+ var/datum/market_item/hostage/market_item = new(src, black_market_price || ransom_price)
+ SSblackmarket.markets[/datum/market/blackmarket].add_item(market_item)
+
+ if(mind)
+ ADD_TRAIT(mind, TRAIT_HAS_BEEN_KIDNAPPED, TRAIT_GENERIC)
+ return market_item
+
/// Admin only proc for making the mob hallucinate a certain thing
/mob/living/proc/admin_give_hallucination(mob/admin)
if(!admin || !check_rights(NONE))
diff --git a/tgstation.dme b/tgstation.dme
index 298e6aa7d79..171fbd2a581 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -293,6 +293,7 @@
#include "code\__DEFINES\dcs\signals\signals_backpack.dm"
#include "code\__DEFINES\dcs\signals\signals_beam.dm"
#include "code\__DEFINES\dcs\signals\signals_bitrunning.dm"
+#include "code\__DEFINES\dcs\signals\signals_blackmarket.dm"
#include "code\__DEFINES\dcs\signals\signals_blob.dm"
#include "code\__DEFINES\dcs\signals\signals_bot.dm"
#include "code\__DEFINES\dcs\signals\signals_camera.dm"
@@ -3655,7 +3656,9 @@
#include "code\modules\cargo\markets\market_uplink.dm"
#include "code\modules\cargo\markets\market_items\clothing.dm"
#include "code\modules\cargo\markets\market_items\consumables.dm"
+#include "code\modules\cargo\markets\market_items\hostages.dm"
#include "code\modules\cargo\markets\market_items\misc.dm"
+#include "code\modules\cargo\markets\market_items\stolen_goods.dm"
#include "code\modules\cargo\markets\market_items\tools.dm"
#include "code\modules\cargo\markets\market_items\weapons.dm"
#include "code\modules\cargo\packs\_packs.dm"