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"