diff --git a/code/modules/mining/equipment_locker.dm b/code/modules/mining/equipment_locker.dm
index c821bf84baa..e6a4330943c 100644
--- a/code/modules/mining/equipment_locker.dm
+++ b/code/modules/mining/equipment_locker.dm
@@ -413,7 +413,7 @@
new /datum/data/mining_equipment("Stabilizing Serum", /obj/item/hivelordstabilizer, 400),
new /datum/data/mining_equipment("Space Cash", /obj/item/stack/spacecash/c1000, 2000),
new /datum/data/mining_equipment("Point Transfer Card", /obj/item/card/mining_point_card, 500),
-// new /datum/data/mining_equipment("Fulton Beacon", /obj/item/fulton_core, 400),
+ new /datum/data/mining_equipment("Fulton Beacon", /obj/item/fulton_core, 400),
new /datum/data/mining_equipment("Shelter Capsule", /obj/item/survivalcapsule, 400),
new /datum/data/mining_equipment("GAR Meson Scanners", /obj/item/clothing/glasses/meson/gar, 500),
new /datum/data/mining_equipment("Explorer's Webbing", /obj/item/storage/belt/mining, 500),
@@ -425,7 +425,7 @@
new /datum/data/mining_equipment("Kinetic Accelerator", /obj/item/gun/energy/kinetic_accelerator, 750),
new /datum/data/mining_equipment("Advanced Scanner", /obj/item/t_scanner/adv_mining_scanner, 800),
new /datum/data/mining_equipment("Resonator", /obj/item/resonator, 800),
-// new /datum/data/mining_equipment("Fulton Pack", /obj/item/extraction_pack, 1000),
+ new /datum/data/mining_equipment("Fulton Pack", /obj/item/extraction_pack, 1000),
new /datum/data/mining_equipment("Lazarus Injector", /obj/item/lazarus_injector, 1000),
new /datum/data/mining_equipment("Silver Pickaxe", /obj/item/pickaxe/silver, 1000),
new /datum/data/mining_equipment("Mining Conscription Kit", /obj/item/storage/backpack/duffel/mining_conscript, 1500),
@@ -602,7 +602,7 @@
..()
/obj/machinery/mineral/equipment_vendor/proc/RedeemVoucher(obj/item/mining_voucher/voucher, mob/redeemer)
- var/items = list("Survival Capsule and Explorer's Webbing", "Resonator Kit", "Minebot Kit", "Crusher Kit", "Mining Conscription Kit")
+ var/items = list("Survival Capsule and Explorer's Webbing", "Resonator Kit", "Minebot Kit", "Extraction and Rescue Kit", "Crusher Kit", "Mining Conscription Kit")
var/selection = input(redeemer, "Pick your equipment", "Mining Voucher Redemption") as null|anything in items
if(!selection || !Adjacent(redeemer) || QDELETED(voucher) || voucher.loc != redeemer)
@@ -619,10 +619,10 @@
new /obj/item/mining_drone_cube(drop_location)
new /obj/item/weldingtool/hugetank(drop_location)
new /obj/item/clothing/head/welding(drop_location)
-// if("Extraction and Rescue Kit")
-// new /obj/item/extraction_pack(drop_location)
-// new /obj/item/fulton_core(drop_location)
-// new /obj/item/stack/marker_beacon/thirty(drop_location)
+ if("Extraction and Rescue Kit")
+ new /obj/item/extraction_pack(drop_location)
+ new /obj/item/fulton_core(drop_location)
+ new /obj/item/stack/marker_beacon/thirty(drop_location)
if("Crusher Kit")
new /obj/item/extinguisher/mini(drop_location)
new /obj/item/twohanded/required/kinetic_crusher(drop_location)
diff --git a/code/modules/mining/fulton.dm b/code/modules/mining/fulton.dm
new file mode 100644
index 00000000000..2d09c6a8e7a
--- /dev/null
+++ b/code/modules/mining/fulton.dm
@@ -0,0 +1,195 @@
+GLOBAL_LIST_EMPTY(total_extraction_beacons)
+
+/obj/item/extraction_pack
+ name = "fulton extraction pack"
+ desc = "A balloon that can be used to extract equipment or personnel to a Fulton Recovery Beacon. Anything not bolted down can be moved. Link the pack to a beacon by using the pack in hand."
+ icon = 'icons/obj/fulton.dmi'
+ icon_state = "extraction_pack"
+ w_class = WEIGHT_CLASS_NORMAL
+ var/obj/structure/extraction_point/beacon
+ var/list/beacon_networks = list("station")
+ var/uses_left = 3
+ var/can_use_indoors
+ var/safe_for_living_creatures = TRUE
+ var/max_force_fulton = MOVE_FORCE_STRONG
+
+/obj/item/extraction_pack/examine(mob/user)
+ ..()
+ to_chat(user, "It has [uses_left] use\s remaining.")
+
+/obj/item/extraction_pack/attack_self(mob/user)
+ var/list/possible_beacons = list()
+ for(var/B in GLOB.total_extraction_beacons)
+ var/obj/structure/extraction_point/EP = B
+ if(EP.beacon_network in beacon_networks)
+ possible_beacons += EP
+
+ if(!possible_beacons.len)
+ to_chat(user, "There are no extraction beacons in existence!")
+ return
+
+ else
+ var/A
+
+ A = input("Select a beacon to connect to", "Balloon Extraction Pack", A) as null|anything in possible_beacons
+
+ if(!A)
+ return
+ beacon = A
+ to_chat(user, "You link the extraction pack to the beacon system.")
+
+/obj/item/extraction_pack/afterattack(atom/movable/A, mob/living/carbon/human/user, flag, params)
+ . = ..()
+ if(!beacon)
+ to_chat(user, "[src] is not linked to a beacon, and cannot be used!")
+ return
+ if(!can_use_indoors)
+ var/area/area = get_area(A)
+ if(!area.outdoors)
+ to_chat(user, "[src] can only be used on things that are outdoors!")
+ return
+ if(!flag)
+ return
+ if(!istype(A))
+ return
+ else
+ if(!safe_for_living_creatures && check_for_living_mobs(A))
+ to_chat(user, "[src] is not safe for use with living creatures, they wouldn't survive the trip back!")
+ return
+ if(!isturf(A.loc)) // no extracting stuff inside other stuff
+ return
+ if(A.anchored || (A.move_resist > max_force_fulton))
+ return
+ to_chat(user, "You start attaching the pack to [A]...")
+ if(do_after(user, 50, target = A))
+ to_chat(user, "You attach the pack to [A] and activate it.")
+ if(loc == user && istype(user.back, /obj/item/storage/backpack))
+ var/obj/item/storage/backpack/B = user.back
+ if(B.can_be_inserted(src, stop_messages = TRUE))
+ B.handle_item_insertion(src)
+ uses_left--
+ if(uses_left <= 0)
+ user.drop_item(src)
+ forceMove(A)
+ var/mutable_appearance/balloon
+ var/mutable_appearance/balloon2
+ var/mutable_appearance/balloon3
+ if(isliving(A))
+ var/mob/living/M = A
+ M.Weaken(16) // Keep them from moving during the duration of the extraction
+ M.buckled = 0 // Unbuckle them to prevent anchoring problems
+ else
+ A.anchored = TRUE
+ A.density = FALSE
+ var/obj/effect/extraction_holder/holder_obj = new(A.loc)
+ holder_obj.appearance = A.appearance
+ A.forceMove(holder_obj)
+ balloon2 = mutable_appearance('icons/obj/fulton_balloon.dmi', "fulton_expand")
+ balloon2.pixel_y = 10
+ balloon2.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
+ holder_obj.add_overlay(balloon2)
+ sleep(4)
+ balloon = mutable_appearance('icons/obj/fulton_balloon.dmi', "fulton_balloon")
+ balloon.pixel_y = 10
+ balloon.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
+ holder_obj.cut_overlay(balloon2)
+ holder_obj.add_overlay(balloon)
+ playsound(holder_obj.loc, 'sound/items/fultext_deploy.ogg', 50, 1, -3)
+ animate(holder_obj, pixel_z = 10, time = 20)
+ sleep(20)
+ animate(holder_obj, pixel_z = 15, time = 10)
+ sleep(10)
+ animate(holder_obj, pixel_z = 10, time = 10)
+ sleep(10)
+ animate(holder_obj, pixel_z = 15, time = 10)
+ sleep(10)
+ animate(holder_obj, pixel_z = 10, time = 10)
+ sleep(10)
+ playsound(holder_obj.loc, 'sound/items/fultext_launch.ogg', 50, 1, -3)
+ animate(holder_obj, pixel_z = 1000, time = 30)
+ if(ishuman(A))
+ var/mob/living/carbon/human/L = A
+ L.SetParalysis(0)
+ L.drowsyness = 0
+ L.SetSleeping(0)
+ sleep(30)
+ var/list/flooring_near_beacon = list()
+ for(var/turf/floor in orange(1, beacon))
+ if(floor.density)
+ continue
+ flooring_near_beacon += floor
+ holder_obj.forceMove(pick(flooring_near_beacon))
+ animate(holder_obj, pixel_z = 10, time = 50)
+ sleep(50)
+ animate(holder_obj, pixel_z = 15, time = 10)
+ sleep(10)
+ animate(holder_obj, pixel_z = 10, time = 10)
+ sleep(10)
+ balloon3 = mutable_appearance('icons/obj/fulton_balloon.dmi', "fulton_retract")
+ balloon3.pixel_y = 10
+ balloon3.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
+ holder_obj.cut_overlay(balloon)
+ holder_obj.add_overlay(balloon3)
+ sleep(4)
+ holder_obj.cut_overlay(balloon3)
+ A.anchored = FALSE // An item has to be unanchored to be extracted in the first place.
+ A.density = initial(A.density)
+ animate(holder_obj, pixel_z = 0, time = 5)
+ sleep(5)
+ A.forceMove(holder_obj.loc)
+ qdel(holder_obj)
+ if(uses_left <= 0)
+ qdel(src)
+
+
+/obj/item/fulton_core
+ name = "extraction beacon signaller"
+ desc = "Emits a signal which fulton recovery devices can lock onto. Activate in hand to create a beacon."
+ icon = 'icons/obj/stock_parts.dmi'
+ icon_state = "subspace_amplifier"
+
+/obj/item/fulton_core/attack_self(mob/user)
+ if(do_after(user, 15, target = user) && !QDELETED(src))
+ new /obj/structure/extraction_point(get_turf(user))
+ qdel(src)
+
+/obj/structure/extraction_point
+ name = "fulton recovery beacon"
+ desc = "A beacon for the fulton recovery system. Activate a pack in your hand to link it to a beacon."
+ icon = 'icons/obj/fulton.dmi'
+ icon_state = "extraction_point"
+ anchored = TRUE
+ density = FALSE
+ var/beacon_network = "station"
+
+/obj/structure/extraction_point/New()
+ ..()
+ name += " ([rand(100,999)]) ([get_location_name(src)])"
+ GLOB.total_extraction_beacons += src
+
+/obj/structure/extraction_point/Destroy()
+ GLOB.total_extraction_beacons -= src
+ return ..()
+
+/obj/effect/extraction_holder
+ name = "extraction holder"
+ desc = "you shouldnt see this"
+ var/atom/movable/stored_obj
+
+/obj/item/extraction_pack/proc/check_for_living_mobs(atom/A)
+ if(isliving(A))
+ var/mob/living/L = A
+ if(L.stat != DEAD)
+ return TRUE
+ for(var/thing in A.GetAllContents())
+ if(isliving(A))
+ var/mob/living/L = A
+ if(L.stat != DEAD)
+ return TRUE
+ return FALSE
+
+/obj/effect/extraction_holder/singularity_pull()
+ return
+
+/obj/effect/extraction_holder/singularity_pull()
+ return
\ No newline at end of file
diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm
index b6a7e7bb28f..9da43d08fbf 100644
--- a/code/modules/shuttle/supply.dm
+++ b/code/modules/shuttle/supply.dm
@@ -247,7 +247,8 @@
/obj/machinery/clonepod,
/obj/effect/hierophant,
/obj/item/warp_cube,
- /obj/machinery/quantumpad
+ /obj/machinery/quantumpad,
+ /obj/structure/extraction_point
)
if(A)
if(is_type_in_list(A, blacklist))
diff --git a/icons/obj/fulton.dmi b/icons/obj/fulton.dmi
new file mode 100644
index 00000000000..c56c275b3b1
Binary files /dev/null and b/icons/obj/fulton.dmi differ
diff --git a/icons/obj/fulton_balloon.dmi b/icons/obj/fulton_balloon.dmi
new file mode 100644
index 00000000000..5a1fa98e148
Binary files /dev/null and b/icons/obj/fulton_balloon.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 86eb2825b04..665095fe782 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -1641,6 +1641,7 @@
#include "code\modules\mining\coins.dm"
#include "code\modules\mining\equipment_locker.dm"
#include "code\modules\mining\explorer_gear.dm"
+#include "code\modules\mining\fulton.dm"
#include "code\modules\mining\machine_processing.dm"
#include "code\modules\mining\machine_stacking.dm"
#include "code\modules\mining\machine_unloading.dm"
diff --git a/sound/items/fultext_deploy.ogg b/sound/items/fultext_deploy.ogg
new file mode 100644
index 00000000000..2433b9e0fe3
Binary files /dev/null and b/sound/items/fultext_deploy.ogg differ
diff --git a/sound/items/fultext_launch.ogg b/sound/items/fultext_launch.ogg
new file mode 100644
index 00000000000..2c874419927
Binary files /dev/null and b/sound/items/fultext_launch.ogg differ