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"