diff --git a/_maps/map_files/generic/CentCom.dmm b/_maps/map_files/generic/CentCom.dmm
index f6687b01e25..77b9ba8345e 100644
--- a/_maps/map_files/generic/CentCom.dmm
+++ b/_maps/map_files/generic/CentCom.dmm
@@ -167,7 +167,7 @@
/obj/machinery/door/airlock/wood{
name = "Kitchen"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"aG" = (
/obj/structure/railing/corner,
@@ -584,7 +584,7 @@
/obj/item/kirbyplants{
icon_state = "plant-10"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"bO" = (
/obj/effect/landmark/holding_facility,
@@ -1103,7 +1103,7 @@
/obj/structure/mineral_door/paperframe{
name = "Dining Hall"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"dk" = (
/obj/effect/turf_decal/siding/wood{
@@ -1115,7 +1115,7 @@
/obj/structure/mineral_door/paperframe{
name = "Botanical Garden"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"dl" = (
/obj/effect/turf_decal/siding/wood,
@@ -1125,7 +1125,7 @@
/obj/structure/mineral_door/paperframe{
name = "Guest Suite *B"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"dm" = (
/obj/machinery/seed_extractor,
@@ -1162,7 +1162,7 @@
/area/centcom/central_command_areas/holding)
"dt" = (
/obj/structure/closet/crate/freezer/blood,
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"du" = (
/obj/effect/turf_decal/tile/bar,
@@ -1456,7 +1456,7 @@
/obj/structure/mineral_door/paperframe{
name = "Guest Suite *A"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"el" = (
/obj/structure/filingcabinet/medical,
@@ -1711,7 +1711,7 @@
/obj/machinery/door/airlock/wood{
name = "Washroom"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"eU" = (
/obj/machinery/hydroponics/constructable,
@@ -2121,7 +2121,7 @@
"ge" = (
/obj/machinery/defibrillator_mount/directional/south,
/obj/machinery/stasis,
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"gf" = (
/obj/structure/flora/tree/pine/style_random,
@@ -2403,7 +2403,7 @@
name = "Momo";
radio_key = null
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"gX" = (
/obj/structure/table/reinforced,
@@ -2418,7 +2418,7 @@
/turf/open/floor/grass,
/area/centcom/central_command_areas/holding)
"hb" = (
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"hc" = (
/obj/structure/table/reinforced,
@@ -2617,7 +2617,7 @@
/obj/structure/mineral_door/paperframe{
name = "East Wing"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"hz" = (
/obj/effect/turf_decal/tile/neutral/fourcorners,
@@ -2720,7 +2720,7 @@
},
/obj/machinery/washing_machine,
/obj/machinery/light/directional/east,
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"hL" = (
/obj/effect/turf_decal/tile/yellow/half,
@@ -6305,7 +6305,7 @@
dir = 1
},
/obj/effect/turf_decal/siding/wood,
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"sq" = (
/obj/machinery/computer/shuttle/white_ship{
@@ -6398,7 +6398,7 @@
"sF" = (
/obj/effect/turf_decal/tile/dark/fourcorners,
/obj/effect/turf_decal/siding/wood,
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"sG" = (
/obj/docking_port/stationary{
@@ -6942,7 +6942,7 @@
pixel_y = 3
},
/obj/item/clothing/suit/hawaiian,
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"ur" = (
/obj/machinery/pdapainter,
@@ -9104,7 +9104,7 @@
/obj/effect/turf_decal/siding/wood{
dir = 8
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"AH" = (
/turf/open/floor/iron,
@@ -10054,7 +10054,7 @@
name = "Bar"
},
/obj/machinery/duct,
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"Do" = (
/obj/structure/railing/corner{
@@ -10129,7 +10129,7 @@
/obj/structure/mineral_door/paperframe{
name = "Dojo"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"Dx" = (
/obj/structure/chair/bronze{
@@ -10990,7 +10990,7 @@
/obj/structure/mineral_door/paperframe{
name = "Blue"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"FV" = (
/obj/structure/chair/pew/left{
@@ -13115,7 +13115,7 @@
/obj/structure/mineral_door/paperframe{
name = "Performance Room"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"MI" = (
/obj/machinery/light/directional/north,
@@ -13740,7 +13740,7 @@
/obj/structure/mineral_door/paperframe{
name = "Shrine"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"Ov" = (
/obj/structure/table/reinforced/rglass,
@@ -14018,7 +14018,7 @@
/obj/structure/sign/painting/library{
pixel_y = 32
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"Pg" = (
/obj/machinery/computer/auxiliary_base/directional/north,
@@ -14431,7 +14431,7 @@
/obj/structure/mineral_door/paperframe{
name = "Electrical Room"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"Qn" = (
/obj/effect/turf_decal/siding/purple{
@@ -14476,7 +14476,7 @@
/obj/effect/turf_decal/siding/wood{
dir = 1
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"Qv" = (
/obj/effect/turf_decal/tile/red{
@@ -14608,7 +14608,7 @@
/obj/machinery/door/airlock/wood{
name = "Toilet"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"QR" = (
/obj/structure/chair/office/light{
@@ -14787,7 +14787,7 @@
"Rk" = (
/obj/machinery/defibrillator_mount/directional/north,
/obj/machinery/stasis,
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"Rl" = (
/obj/structure/chair/comfy/brown{
@@ -14803,7 +14803,7 @@
/obj/structure/table/reinforced/plastitaniumglass,
/obj/item/storage/basket,
/obj/machinery/light/directional/west,
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"Rn" = (
/obj/structure/chair/sofa/bench/right{
@@ -14923,7 +14923,7 @@
/obj/structure/sign/painting/library{
pixel_y = 32
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"RE" = (
/obj/effect/turf_decal/siding/wood,
@@ -14933,7 +14933,7 @@
/obj/structure/mineral_door/paperframe{
name = "Guest Suite *D"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"RF" = (
/obj/structure/table/reinforced,
@@ -15051,7 +15051,7 @@
/obj/structure/mineral_door/paperframe{
name = "Red"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"RZ" = (
/obj/effect/turf_decal/trimline/red,
@@ -15064,7 +15064,7 @@
name = "Hattori";
radio_key = null
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"Sb" = (
/obj/structure/table/wood,
@@ -15237,7 +15237,7 @@
/area/centcom/central_command_areas/holding)
"SF" = (
/obj/effect/turf_decal/siding/wood,
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"SG" = (
/obj/machinery/light/directional/south,
@@ -15306,7 +15306,7 @@
/obj/structure/mineral_door/paperframe{
name = "Guest Suite *C"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"SQ" = (
/obj/structure/filingcabinet/medical,
@@ -15932,7 +15932,7 @@
/obj/structure/mineral_door/paperframe{
name = "Spectator's Lounge"
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"Uq" = (
/obj/structure/flora/grass/both/style_random,
@@ -16285,7 +16285,7 @@
icon_state = "plant-10"
},
/obj/machinery/light/small/directional/south,
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"Vk" = (
/obj/effect/turf_decal/tile/brown,
@@ -16415,7 +16415,7 @@
/obj/effect/turf_decal/siding/wood{
dir = 1
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"VH" = (
/obj/effect/turf_decal/tile/bar,
@@ -16497,7 +16497,7 @@
dir = 1
},
/obj/effect/turf_decal/siding/wood,
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"VT" = (
/obj/structure/rack,
@@ -16786,7 +16786,7 @@
/obj/effect/turf_decal/tile/dark/fourcorners,
/obj/effect/turf_decal/siding/wood,
/obj/machinery/washing_machine,
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"WA" = (
/obj/item/kirbyplants{
@@ -17186,7 +17186,7 @@
/area/centcom/tdome/administration)
"Xx" = (
/obj/machinery/vending/dinnerware,
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"Xy" = (
/obj/machinery/door/airlock/external/ruin{
@@ -17292,7 +17292,7 @@
/obj/item/organ/heart/cybernetic/tier2,
/obj/item/organ/heart/cybernetic/tier2,
/obj/item/defibrillator,
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"XJ" = (
/obj/structure/flora/bush/lavendergrass/style_random,
@@ -17935,7 +17935,7 @@
/obj/structure/window/paperframe{
can_atmos_pass = 0
},
-/turf/open/floor/sepia,
+/turf/open/floor/iron/sepia,
/area/centcom/central_command_areas/holding)
"ZC" = (
/obj/effect/turf_decal/stripes/line{
diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index 24d8417204c..9844097573b 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -249,3 +249,10 @@ GLOBAL_LIST_INIT(ai_employers, list(
#define OBJECTIVE_STATE_FAILED 4
/// If the objective is no longer valid
#define OBJECTIVE_STATE_INVALID 5
+
+/// Weights for traitor objective categories
+#define OBJECTIVE_WEIGHT_TINY 5
+#define OBJECTIVE_WEIGHT_SMALL 7
+#define OBJECTIVE_WEIGHT_DEFAULT 10
+#define OBJECTIVE_WEIGHT_BIG 15
+#define OBJECTIVE_WEIGHT_HUGE 20
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 5690b8424fd..c8b68b61440 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -535,6 +535,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_ALLOW_HERETIC_CASTING "allow_heretic_casting"
/// Designates a heart as a living heart for a heretic.
#define TRAIT_LIVING_HEART "living_heart"
+/// Prevents the same person from being chosen multiple times for kidnapping objective
+#define TRAIT_HAS_BEEN_KIDNAPPED "has_been_kidnapped"
//quirk traits
#define TRAIT_ALCOHOL_TOLERANCE "alcohol_tolerance"
diff --git a/code/controllers/subsystem/traitor.dm b/code/controllers/subsystem/traitor.dm
index db9e687e9ec..cbbba49d086 100644
--- a/code/controllers/subsystem/traitor.dm
+++ b/code/controllers/subsystem/traitor.dm
@@ -37,6 +37,8 @@ SUBSYSTEM_DEF(traitor)
var/generate_objectives = TRUE
/// Objectives that have been completed by type. Used for limiting objectives.
var/list/taken_objectives_by_type = list()
+ /// Contains 3 areas: 2 areas to scan in order to triangulate the third one which is the structural weakpoint itself
+ var/list/station_weakpoints = list()
/datum/controller/subsystem/traitor/Initialize(start_timeofday)
. = ..()
@@ -51,6 +53,31 @@ SUBSYSTEM_DEF(traitor)
log_world("[configuration_path] has an invalid type ([typepath]) that doesn't exist in the codebase! Please correct or remove [typepath]")
configuration_data[actual_typepath] = data[typepath]
+ /// List of high-security areas that we pick required ones from
+ var/list/allowed_areas = typecacheof(list(/area/station/command,
+ /area/station/cargo/qm,
+ /area/station/comms,
+ /area/station/engineering,
+ /area/station/science,
+ /area/station/security,
+ ))
+
+ var/list/blacklisted_areas = typecacheof(list(/area/station/engineering/hallway,
+ /area/station/engineering/lobby,
+ /area/station/engineering/storage,
+ /area/station/science/lobby,
+ /area/station/science/test_area,
+ /area/station/security/prison,
+ ))
+
+ var/list/possible_areas = GLOB.the_station_areas.Copy()
+ for(var/area/possible_area as anything in possible_areas)
+ if(!is_type_in_typecache(possible_area, allowed_areas) || initial(possible_area.outdoors) || is_type_in_typecache(possible_area, blacklisted_areas))
+ possible_areas -= possible_area
+
+ for(var/i in 1 to 3)
+ station_weakpoints += pick_n_take(possible_areas)
+
/datum/controller/subsystem/traitor/fire(resumed)
var/player_count = length(GLOB.alive_player_list)
// Has a maximum of 1 minute, however the value can be lower if there are lower players than the ideal
diff --git a/code/modules/antagonists/traitor/objective_category.dm b/code/modules/antagonists/traitor/objective_category.dm
index e64086ad922..dcd41cd8518 100644
--- a/code/modules/antagonists/traitor/objective_category.dm
+++ b/code/modules/antagonists/traitor/objective_category.dm
@@ -65,4 +65,4 @@
/// Assoc list of objectives by type mapped to their weight. Can also contain lists of objectives mapped to weight
var/list/objectives = list()
/// The weight of the category. How likely this category is to be chosen.
- var/weight = 1
+ var/weight = OBJECTIVE_WEIGHT_DEFAULT
diff --git a/code/modules/antagonists/traitor/objectives/eyesnatching.dm b/code/modules/antagonists/traitor/objectives/eyesnatching.dm
new file mode 100644
index 00000000000..0e87513eadd
--- /dev/null
+++ b/code/modules/antagonists/traitor/objectives/eyesnatching.dm
@@ -0,0 +1,219 @@
+/datum/traitor_objective_category/eyesnatching
+ name = "Eyesnatching"
+ objectives = list(
+ /datum/traitor_objective/eyesnatching = 1,
+ /datum/traitor_objective/eyesnatching/heads = 1,
+ )
+ weight = OBJECTIVE_WEIGHT_TINY
+
+/datum/traitor_objective/eyesnatching
+ name = "Steal %TARGET%'s (%JOB TITLE%) eyes"
+ description = "%TARGET% messed with the wrong people. Steal their eyes to teach them a lesson. You will be provided an experimental eyesnatcher device to aid you in your mission."
+
+ progression_reward = list(4 MINUTES, 8 MINUTES)
+ telecrystal_reward = list(1, 2)
+
+ // The code below is for limiting how often you can get this objective. You will get this objective at a maximum of maximum_objectives_in_period every objective_period
+ /// The objective period at which we consider if it is an 'objective'. Set to 0 to accept all objectives.
+ var/objective_period = 15 MINUTES
+ /// The maximum number of objectives we can get within this period.
+ var/maximum_objectives_in_period = 3
+
+ /// Who we're stealing eyes from
+ var/mob/living/victim
+ /// If we're targeting heads of staff or not
+ var/heads_of_staff = FALSE
+ /// Have we already spawned an eyesnatcher
+ var/spawned_eyesnatcher = FALSE
+
+/datum/traitor_objective/eyesnatching/supported_configuration_changes()
+ . = ..()
+ . += NAMEOF(src, objective_period)
+ . += NAMEOF(src, maximum_objectives_in_period)
+
+/datum/traitor_objective/eyesnatching/New(datum/uplink_handler/handler)
+ . = ..()
+ AddComponent(/datum/component/traitor_objective_limit_per_time, \
+ /datum/traitor_objective/eyesnatching, \
+ time_period = objective_period, \
+ maximum_objectives = maximum_objectives_in_period \
+ )
+
+/datum/traitor_objective/eyesnatching/heads
+ progression_reward = list(6 MINUTES, 12 MINUTES)
+ telecrystal_reward = list(2, 3)
+
+ heads_of_staff = TRUE
+
+/datum/traitor_objective/eyesnatching/generate_objective(datum/mind/generating_for, list/possible_duplicates)
+ var/list/possible_targets = list()
+ var/try_target_late_joiners = FALSE
+ if(generating_for.late_joiner)
+ try_target_late_joiners = TRUE
+
+ for(var/datum/mind/possible_target as anything in get_crewmember_minds())
+ if(possible_target == generating_for)
+ continue
+
+ if(!ishuman(possible_target.current))
+ continue
+
+ if(possible_target.current.stat == DEAD)
+ continue
+
+ if(possible_target.has_antag_datum(/datum/antagonist/traitor))
+ continue
+
+ if(heads_of_staff)
+ if(!(possible_target.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND))
+ continue
+ else
+ if(possible_target.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND)
+ continue
+
+ var/mob/living/carbon/human/targets_current = possible_target.current
+ if(!targets_current.getorgan(/obj/item/organ/eyes))
+ continue
+
+ possible_targets += targets_current
+
+ for(var/datum/traitor_objective/eyesnatching/objective as anything in possible_duplicates)
+ possible_targets -= objective.victim
+
+ if(try_target_late_joiners)
+ var/list/all_possible_targets = possible_targets.Copy()
+ for(var/datum/mind/possible_target as anything in all_possible_targets)
+ if(!possible_target.late_joiner)
+ possible_targets -= possible_target
+
+ if(!possible_targets.len)
+ possible_targets = all_possible_targets
+
+ if(!possible_targets.len)
+ return FALSE //MISSION FAILED, WE'LL GET EM NEXT TIME
+
+ victim = pick(possible_targets)
+ var/datum/mind/victim_mind = victim.mind
+
+ replace_in_name("%TARGET%", victim_mind.name)
+ replace_in_name("%JOB TITLE%", victim_mind.assigned_role.title)
+ RegisterSignal(victim, COMSIG_CARBON_LOSE_ORGAN, .proc/check_eye_removal)
+ AddComponent(/datum/component/traitor_objective_register, victim, fail_signals = COMSIG_PARENT_QDELETING)
+
+/datum/traitor_objective/eyesnatching/proc/check_eye_removal(datum/source, obj/item/organ/eyes/removed)
+ SIGNAL_HANDLER
+
+ if(!istype(removed))
+ return
+
+ succeed_objective()
+
+/datum/traitor_objective/eyesnatching/generate_ui_buttons(mob/user)
+ var/list/buttons = list()
+ if(!spawned_eyesnatcher)
+ buttons += add_ui_button("", "Pressing this will materialize an eyesnatcher, which can be used on incapacitaded or restrained targets to forcefully remove their eyes.", "syringe", "eyesnatcher")
+ return buttons
+
+/datum/traitor_objective/eyesnatching/ui_perform_action(mob/living/user, action)
+ . = ..()
+ switch(action)
+ if("eyesnatcher")
+ if(spawned_eyesnatcher)
+ return
+ spawned_eyesnatcher = TRUE
+ var/obj/item/eyesnatcher/eyesnatcher = new(user.drop_location())
+ user.put_in_hands(eyesnatcher)
+ eyesnatcher.balloon_alert(user, "the eyesnatcher materializes in your hand")
+
+/obj/item/eyesnatcher
+ name = "portable eyeball extractor"
+ desc = "An overly complicated device that can pierce target's skull and extract their eyeballs if enough brute force is applied."
+ icon = 'icons/obj/surgery.dmi'
+ icon_state = "eyesnatcher"
+ base_icon_state = "eyesnatcher"
+ inhand_icon_state = "hypo"
+ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+ throwforce = 0
+ w_class = WEIGHT_CLASS_SMALL
+ throw_speed = 3
+ throw_range = 5
+ var/used = FALSE
+
+/obj/item/eyesnatcher/update_icon_state()
+ . = ..()
+ icon_state = "[base_icon_state][used ? "-used" : ""]"
+
+/obj/item/eyesnatcher/attack(mob/living/carbon/human/victim, mob/living/user, params)
+ if(!istype(victim) || !victim.Adjacent(user)) //No TK use
+ return ..()
+
+ var/obj/item/organ/eyes/eyeballies = victim.getorganslot(ORGAN_SLOT_EYES)
+ var/obj/item/bodypart/head/head = victim.get_bodypart(BODY_ZONE_HEAD)
+
+ if(!eyeballies || victim.is_eyes_covered())
+ return ..()
+
+ if((head && head.eyes != eyeballies) || eyeballies.zone != BODY_ZONE_HEAD)
+ to_chat(user, span_warning("You don't know how to apply [src] to the abomination that [victim] is!"))
+ return ..()
+
+ if(!head || !istype(head))
+ return ..()
+
+ user.do_attack_animation(victim, used_item = src)
+ victim.visible_message(span_warning("[user] presses [src] against [victim]'s skull!"), span_userdanger("[user] presses [src] against your skull!"))
+ if(!do_after(user, 5 SECONDS, target = victim, extra_checks = CALLBACK(src, .proc/eyeballs_exist, eyeballies, head, victim)))
+ return
+
+ to_chat(victim, span_userdanger("You feel [src] pushing at your skull!"))
+ to_chat(user, span_notice("You apply more pressure to [src]."))
+ if(!do_after(user, 5 SECONDS, target = victim, extra_checks = CALLBACK(src, .proc/eyeballs_exist, eyeballies, head, victim)))
+ return
+
+ var/datum/wound/blunt/severe/severe_wound_type = /datum/wound/blunt/severe
+ var/datum/wound/blunt/critical/critical_wound_type = /datum/wound/blunt/critical
+ victim.apply_damage(20, BRUTE, BODY_ZONE_HEAD, wound_bonus = rand(initial(severe_wound_type.threshold_minimum), initial(critical_wound_type.threshold_minimum) + 10))
+ victim.visible_message(
+ span_danger("[src] pierces through [victim]'s skull, horribly mutilating their eyes!"),
+ span_userdanger("Something penetrates your skull, horribly mutilating your eyes! Holy fuck!"),
+ span_hear("You hear a sickening sound of metal piercing flesh!")
+ )
+ eyeballies.applyOrganDamage(eyeballies.maxHealth)
+ victim.emote("scream")
+ playsound(victim, "sound/effects/wounds/crackandbleed.ogg", 100)
+ log_combat(user, victim, "pierced skull of", src)
+
+ if(!do_after(user, 5 SECONDS, target = victim, extra_checks = CALLBACK(src, .proc/eyeballs_exist, eyeballies, head, victim)))
+ return
+
+ if(!HAS_TRAIT(victim, TRAIT_BLIND))
+ to_chat(victim, span_userdanger("You suddenly go blind!"))
+
+ to_chat(user, span_notice("You successfully extract [victim]'s eyeballs using [src]."))
+ victim.emote("scream")
+ playsound(victim, 'sound/surgery/retractor2.ogg', 100, TRUE)
+ playsound(victim, 'sound/effects/pop.ogg', 100, TRAIT_MUTE)
+ eyeballies.Remove(victim)
+ eyeballies.forceMove(get_turf(victim))
+ used = TRUE
+ desc += " It has been used up."
+ update_icon()
+
+/obj/item/eyesnatcher/proc/eyeballs_exist(obj/item/organ/eyes/eyeballies, obj/item/bodypart/head, mob/living/carbon/human/victim)
+ if(!eyeballies || QDELETED(eyeballies))
+ return FALSE
+
+ if(!head || QDELETED(head))
+ return FALSE
+
+ if(!victim || QDELETED(victim))
+ return FALSE
+
+ if(eyeballies.owner != victim)
+ return FALSE
+
+ if(head.owner != victim || head.eyes != eyes)
+ return FALSE
+
+ return TRUE
diff --git a/code/modules/antagonists/traitor/objectives/kidnapping.dm b/code/modules/antagonists/traitor/objectives/kidnapping.dm
new file mode 100644
index 00000000000..b740a72d316
--- /dev/null
+++ b/code/modules/antagonists/traitor/objectives/kidnapping.dm
@@ -0,0 +1,293 @@
+/datum/traitor_objective_category/kidnapping
+ name = "Kidnap Personnel"
+ objectives = list( //Similar weights to destroy heirloom objectives
+ list(
+ /datum/traitor_objective/kidnapping/common = 20,
+ /datum/traitor_objective/kidnapping/less_common = 1,
+ ) = 4,
+ /datum/traitor_objective/kidnapping/uncommon = 3,
+ /datum/traitor_objective/kidnapping/rare = 2,
+ /datum/traitor_objective/kidnapping/captain = 1
+ )
+
+/datum/traitor_objective/kidnapping
+ name = "Kidnap %TARGET% the %JOB TITLE% and deliver them to %AREA%"
+ description = "%TARGET% holds extremely important information regarding secret NT projects - and you'll need to kidnap and deliver them to %AREA%, where our transport pod will be waiting. \
+ You'll get additional reward if %TARGET% is delivered alive."
+
+ abstract_type = /datum/traitor_objective/kidnapping
+
+ //this is a prototype so this progression is for all basic level kill objectives
+ progression_reward = list(2 MINUTES, 4 MINUTES)
+ telecrystal_reward = list(1, 2)
+
+ /// The jobs that this objective is targetting.
+ var/list/target_jobs
+ /// The person we need to kidnap
+ var/mob/living/victim
+ /// Area that the victim needs to be delivered to
+ var/area/dropoff_area
+ /// Have we called the pod yet?
+ var/pod_called = FALSE
+ /// How much TC do we get from sending the target alive
+ var/alive_bonus = 0
+ /// All stripped victims belongings
+ var/list/victim_belogings = list()
+
+/datum/traitor_objective/kidnapping/common
+ progression_maximum = 30 MINUTES
+ target_jobs = list(
+ // Medical
+ /datum/job/doctor,
+ /datum/job/paramedic,
+ /datum/job/psychologist,
+ /datum/job/chemist,
+ // Service
+ /datum/job/clown,
+ /datum/job/botanist,
+ /datum/job/janitor,
+ /datum/job/mime,
+ /datum/job/lawyer,
+ /datum/job/chaplain,
+ /datum/job/bartender,
+ /datum/job/curator,
+ // Cargo
+ /datum/job/cargo_technician,
+ // Science
+ /datum/job/geneticist,
+ /datum/job/roboticist,
+ // Engineering
+ /datum/job/station_engineer,
+ /datum/job/atmospheric_technician,
+ )
+
+/datum/traitor_objective/kidnapping/less_common
+ progression_maximum = 15 MINUTES
+ target_jobs = list(
+ /datum/job/assistant
+ )
+
+/datum/traitor_objective/kidnapping/uncommon //Hard to fish out victims
+ progression_maximum = 45 MINUTES
+ target_jobs = list(
+ // Medical
+ /datum/job/virologist,
+ // Cargo
+ /datum/job/quartermaster,
+ /datum/job/shaft_miner,
+ // Service
+ /datum/job/cook,
+ // Science
+ /datum/job/scientist,
+ )
+
+ progression_reward = list(4 MINUTES, 8 MINUTES)
+ telecrystal_reward = list(1, 2)
+ alive_bonus = 1
+
+/datum/traitor_objective/kidnapping/rare
+ progression_minimum = 15 MINUTES
+ progression_maximum = 60 MINUTES
+ target_jobs = list(
+ // Security
+ /datum/job/security_officer,
+ /datum/job/warden,
+ /datum/job/detective,
+ // Heads of staff
+ /datum/job/head_of_personnel,
+ /datum/job/chief_medical_officer,
+ /datum/job/research_director,
+ )
+
+ progression_reward = list(8 MINUTES, 12 MINUTES)
+ telecrystal_reward = list(1, 2)
+ alive_bonus = 2
+
+/datum/traitor_objective/kidnapping/captain
+ progression_minimum = 30 MINUTES
+ target_jobs = list(
+ /datum/job/head_of_security,
+ /datum/job/captain
+ )
+
+ progression_reward = list(12 MINUTES, 16 MINUTES)
+ telecrystal_reward = list(2, 3)
+ alive_bonus = 2
+
+/datum/traitor_objective/kidnapping/generate_objective(datum/mind/generating_for, list/possible_duplicates)
+ var/list/possible_targets = list()
+ for(var/datum/mind/possible_target as anything in get_crewmember_minds())
+ if(possible_target == generating_for)
+ continue
+
+ if(!ishuman(possible_target.current))
+ continue
+
+ if(possible_target.current.stat == DEAD)
+ continue
+
+ if(HAS_TRAIT(possible_target, TRAIT_HAS_BEEN_KIDNAPPED))
+ continue
+
+ if(possible_target.has_antag_datum(/datum/antagonist/traitor))
+ continue
+
+ if(!(possible_target.assigned_role.type in target_jobs))
+ continue
+
+ possible_targets += possible_target
+
+ for(var/datum/traitor_objective/kidnapping/objective as anything in possible_duplicates)
+ possible_targets -= objective.victim.mind
+
+ if(!length(possible_targets))
+ return FALSE
+
+ var/datum/mind/target_mind = pick(possible_targets)
+ victim = target_mind.current
+ AddComponent(/datum/component/traitor_objective_register, victim, fail_signals = COMSIG_PARENT_QDELETING)
+ var/list/possible_areas = GLOB.the_station_areas.Copy()
+ for(var/area/possible_area as anything in possible_areas)
+ if(istype(possible_area, /area/station/hallway) || istype(possible_area, /area/station/security) || initial(possible_area.outdoors))
+ possible_areas -= possible_area
+
+ dropoff_area = pick(possible_areas)
+ replace_in_name("%TARGET%", target_mind.name)
+ replace_in_name("%JOB TITLE%", target_mind.assigned_role.title)
+ replace_in_name("%AREA%", initial(dropoff_area.name))
+ return TRUE
+
+/datum/traitor_objective/kidnapping/ungenerate_objective()
+ victim = null
+ dropoff_area = null
+
+/datum/traitor_objective/kidnapping/generate_ui_buttons(mob/user)
+ var/list/buttons = list()
+ if(!pod_called)
+ buttons += add_ui_button("Call Extraction Pod", "Pressing this will call down an extraction pod.", "rocket", "call_pod")
+ return buttons
+
+/datum/traitor_objective/kidnapping/ui_perform_action(mob/living/user, action)
+ . = ..()
+ switch(action)
+ if("call_pod")
+ if(pod_called)
+ return
+ var/area/user_area = get_area(user)
+ var/area/victim_area = get_area(victim)
+
+ if(user_area.type != dropoff_area)
+ to_chat(user, span_warning("You must be in [initial(dropoff_area.name)] to call the extraction pod."))
+ return
+
+ if(victim_area.type != dropoff_area)
+ to_chat(user, span_warning("[victim.real_name] must be in [initial(dropoff_area.name)] for you to call the extraction pod."))
+ return
+
+ call_pod(user)
+
+/datum/traitor_objective/kidnapping/proc/call_pod(mob/living/user)
+ pod_called = TRUE
+ var/obj/structure/closet/supplypod/extractionpod/new_pod = new()
+ RegisterSignal(new_pod, COMSIG_ATOM_ENTERED, .proc/enter_check)
+ new /obj/effect/pod_landingzone(get_turf(user), new_pod)
+
+/datum/traitor_objective/kidnapping/proc/enter_check(obj/structure/closet/supplypod/extractionpod/source, entered_atom)
+ if(!istype(source))
+ CRASH("Kidnapping objective's enter_check called with source being not an extraction pod: [source ? source.type : "N/A"]")
+
+ if(!ishuman(entered_atom))
+ return
+
+ 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)
+ if(belonging == sent_mob.get_item_by_slot(ITEM_SLOT_ICLOTHING) || belonging == sent_mob.get_item_by_slot(ITEM_SLOT_FEET))
+ continue
+
+ sent_mob.transferItemToLoc(belonging)
+ victim_belogings.Add(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)
+
+ addtimer(CALLBACK(src, .proc/handle_victim, sent_mob), 1.5 SECONDS)
+
+ if(sent_mob != victim)
+ fail_objective(penalty_cost = telecrystal_penalty)
+ source.startExitSequence(source)
+ return
+
+ if(sent_mob.stat != DEAD)
+ telecrystal_reward += alive_bonus
+
+ succeed_objective()
+ source.startExitSequence(source)
+
+/datum/traitor_objective/kidnapping/proc/handle_victim(mob/living/carbon/human/sent_mob)
+ addtimer(CALLBACK(src, .proc/return_victim, sent_mob), 3 MINUTES)
+ if(sent_mob.stat == DEAD)
+ return
+
+ sent_mob.flash_act()
+ sent_mob.adjust_timed_status_effect(10 SECONDS, /datum/status_effect/confusion)
+ sent_mob.adjust_timed_status_effect(10 SECONDS, /datum/status_effect/dizziness)
+ sent_mob.blur_eyes(5 SECONDS)
+ to_chat(sent_mob, span_hypnophrase(span_reallybig("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...\"")))
+
+/datum/traitor_objective/kidnapping/proc/return_victim(mob/living/carbon/human/sent_mob)
+ if(!sent_mob || QDELETED(sent_mob)) //suicided and qdeleted themselves
+ return
+
+ var/list/possible_turfs = list()
+ for(var/turf/open/open_turf in dropoff_area)
+ if(open_turf.is_blocked_turf() || isspaceturf(open_turf))
+ continue
+ possible_turfs += open_turf
+
+ if(!LAZYLEN(possible_turfs))
+ var/turf/new_turf = get_safe_random_station_turf()
+ if(!new_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
+
+ possible_turfs += new_turf
+
+ 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
+
+ do_sparks(8, FALSE, sent_mob)
+ sent_mob.visible_message(span_notice("[sent_mob] vanishes!"))
+ for(var/obj/item/belonging in sent_mob)
+ if(belonging == sent_mob.get_item_by_slot(ITEM_SLOT_ICLOTHING) || belonging == sent_mob.get_item_by_slot(ITEM_SLOT_FEET))
+ continue
+
+ sent_mob.transferItemToLoc(belonging)
+
+ for(var/obj/item/belonging in victim_belogings)
+ belonging.forceMove(return_pod)
+
+ sent_mob.forceMove(return_pod)
+ sent_mob.flash_act()
+ sent_mob.adjust_timed_status_effect(10 SECONDS, /datum/status_effect/confusion)
+ sent_mob.adjust_timed_status_effect(10 SECONDS, /datum/status_effect/dizziness)
+ sent_mob.blur_eyes(5 SECONDS)
+
+ new /obj/effect/pod_landingzone(pick(possible_turfs), return_pod)
+
+
+
diff --git a/code/modules/antagonists/traitor/objectives/locate_weakpoint.dm b/code/modules/antagonists/traitor/objectives/locate_weakpoint.dm
new file mode 100644
index 00000000000..e5066b5c146
--- /dev/null
+++ b/code/modules/antagonists/traitor/objectives/locate_weakpoint.dm
@@ -0,0 +1,257 @@
+/datum/traitor_objective_category/locate_weakpoint
+ name = "Locate And Destroy Weakpoint"
+ objectives = list(
+ /datum/traitor_objective/locate_weakpoint = 1,
+ )
+
+/datum/traitor_objective/locate_weakpoint
+ name = "Triangulate station's structural weakpoint and detonate an explosive charge nearby."
+ description = "You will be given a handheld device that you'll need to use in %AREA1% and %AREA2% in order to triangulate the station's structural weakpoint and detonate an explosive charge there. Warning: Once you start scanning either one of the areas, station's AI will be alerted."
+
+ progression_minimum = 45 MINUTES
+ progression_reward = list(15 MINUTES, 20 MINUTES)
+ telecrystal_reward = list(3, 5)
+
+ var/progression_objectives_minimum = 20 MINUTES
+
+ /// Reference to the weakpoint locator(if we sent one)
+ var/obj/item/weakpoint_locator/locator
+ /// Reference to the ES8 explosive (if we sent one)
+ var/obj/item/grenade/c4/es8/shatter_charge
+ /// Locations we have to use the locator in
+ var/list/triangulation_locations
+ /// Have we located the weakpoint yet?
+ var/weakpoint_found = FALSE
+
+/datum/traitor_objective/locate_weakpoint/generate_objective(datum/mind/generating_for, list/possible_duplicates)
+ ////if(handler.get_completion_progression(/datum/traitor_objective) < progression_objectives_minimum)
+ //// return FALSE
+
+ if(!SStraitor.station_weakpoints || !LAZYLEN(SStraitor.station_weakpoints)) //This means that the weakpoint has already been hit
+ return FALSE
+
+ var/area/weakpoint_area1 = SStraitor.station_weakpoints[1]
+ var/area/weakpoint_area2 = SStraitor.station_weakpoints[2]
+ replace_in_name("%AREA1%", initial(weakpoint_area1.name))
+ replace_in_name("%AREA2%", initial(weakpoint_area2.name))
+ triangulation_locations = list(SStraitor.station_weakpoints[1] = TRUE, SStraitor.station_weakpoints[2] = TRUE)
+ RegisterSignal(generating_for, COMSIG_GLOB_TRAITOR_OBJECTIVE_COMPLETED, .proc/on_global_obj_completed)
+ return TRUE
+
+/datum/traitor_objective/locate_weakpoint/proc/on_global_obj_completed(datum/source, datum/traitor_objective/objective)
+ SIGNAL_HANDLER
+ if(istype(objective, /datum/traitor_objective/locate_weakpoint))
+ fail_objective()
+
+/datum/traitor_objective/locate_weakpoint/generate_ui_buttons(mob/user)
+ var/list/buttons = list()
+ if(!locator)
+ buttons += add_ui_button("", "Pressing this will materialize a weakpoint locator in your hand.", "globe", "locator")
+ if(weakpoint_found && !shatter_charge)
+ buttons += add_ui_button("", "Pressing this will materialize an ES8 explosive charge in your hand.", "bomb", "shatter_charge")
+ return buttons
+
+/datum/traitor_objective/locate_weakpoint/ui_perform_action(mob/living/user, action)
+ . = ..()
+ switch(action)
+ if("locator")
+ if(locator)
+ return
+ locator = new(user.drop_location())
+ user.put_in_hands(locator)
+ locator.balloon_alert(user, "the weakpoint locator materializes in your hand")
+ if("shatter_charge")
+ if(shatter_charge)
+ return
+ shatter_charge = new(user.drop_location())
+ user.put_in_hands(shatter_charge)
+ shatter_charge.balloon_alert(user, "the ES8 charge materializes in your hand")
+
+/datum/traitor_objective/locate_weakpoint/proc/weakpoint_located()
+ description = "Structural weakpoint has been located in %AREA%. Detonate an ES8 explosive charge there to create a shockwave that will severely damage the station."
+ var/area/weakpoint_area = SStraitor.station_weakpoints[3]
+ replace_in_name("%AREA%", initial(weakpoint_area.name))
+ weakpoint_found = TRUE
+
+/datum/traitor_objective/locate_weakpoint/proc/create_shockwave(center_x, center_y, center_z)
+ var/severity = list(EXPLODE_LIGHT, EXPLODE_LIGHT, EXPLODE_LIGHT, EXPLODE_LIGHT, EXPLODE_HEAVY, EXPLODE_HEAVY, EXPLODE_DEVASTATE) //Can't use pick_weight because explode defines are numbers
+ var/wave_amount = rand(5, 8)
+ var/list/bombed_turfs = list()
+ for(var/i in 1 to wave_amount)
+ var/wave_angle = rand(-10, 10) + 360 / wave_amount * i
+ var/wave_distance = rand(17, 25)
+ var/turf/tentacle_ending = locate(clamp(center_x + round(cos(wave_angle) * wave_distance), 1, world.maxx), clamp(center_y + round(sin(wave_angle) * wave_distance), 1, world.maxy), center_z)
+ if(!tentacle_ending) //WUT
+ continue
+
+ var/turf/epicenter = locate(center_x, center_y, center_z)
+ for(var/turf/line_turf in get_line(epicenter, tentacle_ending))
+ for(var/turf/bomb_turf in range(1, line_turf))
+ if((bomb_turf in bombed_turfs) || bomb_turf == epicenter)
+ continue
+ bombed_turfs += bomb_turf
+ var/turf_severity = pick(severity)
+ EX_ACT(line_turf, turf_severity)
+ for(var/atom/victim in line_turf)
+ EX_ACT(victim, turf_severity - 1)
+
+ explosion(tentacle_ending, devastation_range = 1, heavy_impact_range = 3, light_impact_range = 5, explosion_cause = src)
+
+ priority_announce(
+ "Attention crew, it appears that a high-power explosive charge has been detonated in your station's weakpoint, causing severe structural damage.",
+ "[command_name()] High-Priority Update"
+ )
+
+ SStraitor.station_weakpoints = null //Gone
+ succeed_objective()
+
+/obj/item/weakpoint_locator
+ name = "structural weakpoint locator"
+ desc = "A device that can triangulate station's structural weakpoint. It has to be used in %AREA1% and %AREA2% in order to triangulate the weakpoint. Warning: station's AI will be notified as soon as the process starts!"
+ icon = 'icons/obj/device.dmi'
+ icon_state = "weakpoint_locator"
+ inhand_icon_state = "weakpoint_locator"
+ lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
+ throwforce = 0
+ w_class = WEIGHT_CLASS_SMALL
+ throw_speed = 3
+ throw_range = 5
+
+/obj/item/weakpoint_locator/Initialize(mapload)
+ . = ..()
+ var/area/weakpoint_area1 = SStraitor.station_weakpoints[1]
+ var/area/weakpoint_area2 = SStraitor.station_weakpoints[2]
+ desc = replacetext(desc, "%AREA1%", initial(weakpoint_area1.name))
+ desc = replacetext(desc, "%AREA2%", initial(weakpoint_area2.name))
+
+/obj/item/weakpoint_locator/attack_self(mob/living/user, modifiers)
+ . = ..()
+ if(!istype(user) || loc != user || !user.mind) //No TK cheese
+ return
+
+ if(!user.mind.has_antag_datum(/datum/antagonist/traitor))
+ to_chat(user, span_warning("You have zero clue how to use [src]."))
+ return
+
+ var/datum/traitor_objective/locate_weakpoint/objective = get_weakpoint_objective(user)
+ if(!objective || objective.objective_state == OBJECTIVE_STATE_INACTIVE)
+ to_chat(user, span_warning("Your time to use [src] has not come yet."))
+ return
+
+ var/area/user_area = get_area(user)
+ if(!(user_area.type in objective.triangulation_locations))
+ balloon_alert(user, "invalid area!")
+ playsound(user, 'sound/machines/buzz-sigh.ogg', 30, TRUE)
+ return
+
+ if(!objective.triangulation_locations[user_area.type])
+ balloon_alert(user, "already scanned here!")
+ playsound(user, 'sound/machines/buzz-sigh.ogg', 30, TRUE)
+ return
+
+ user.visible_message(span_danger("[user] presses a few buttons on [src] and it starts ominously beeping!"), span_notice("You activate [src] and start scanning the area. Do not exit [get_area_name(user, TRUE)] until the scan finishes!"))
+ playsound(user, 'sound/machines/triple_beep.ogg', 30, TRUE)
+ var/alertstr = span_userdanger("Network Alert: Station network probing attempt detected[user_area?" in [get_area_name(user, TRUE)]":". Unable to pinpoint location"].")
+ for(var/mob/living/silicon/ai/ai_player in GLOB.player_list)
+ to_chat(ai_player, alertstr)
+
+ if(!do_after(user, 3 SECONDS, src, IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE | IGNORE_HELD_ITEM | IGNORE_INCAPACITATED | IGNORE_SLOWDOWNS, extra_checks = CALLBACK(src, .proc/scan_checks, user, user_area, objective)))
+ playsound(user, 'sound/machines/buzz-sigh.ogg', 30, TRUE)
+ return
+
+ playsound(user, 'sound/machines/ding.ogg', 100, TRUE)
+ objective.triangulation_locations[user_area.type] = FALSE
+ for(var/area/scan_area in objective.triangulation_locations)
+ if(objective.triangulation_locations[scan_area])
+ say("Next scanning location is [initial(scan_area.name)]")
+ return
+
+ var/area/weakpoint_location = SStraitor.station_weakpoints[3]
+ to_chat(user, span_notice("Scan finished. Structural weakpoint located in [initial(weakpoint_location.name)]."))
+ objective.weakpoint_located()
+
+/obj/item/weakpoint_locator/proc/scan_checks(mob/living/user, area/user_area, datum/traitor_objective/locate_weakpoint/parent_objective)
+ if(get_area(user) != user_area)
+ return FALSE
+
+ if(parent_objective.objective_state == OBJECTIVE_STATE_FAILED || parent_objective.objective_state == OBJECTIVE_STATE_INVALID)
+ return FALSE
+
+ var/atom/current_loc = loc
+ while(!isturf(current_loc) && !ismob(current_loc))
+ current_loc = current_loc.loc
+
+ if(current_loc != user)
+ return FALSE
+
+ return TRUE
+
+/obj/item/weakpoint_locator/proc/get_weakpoint_objective(mob/living/user)
+ if(!user.mind)
+ return
+
+ for(var/datum/traitor_objective/locate_weakpoint/weakpoint_objecitve in SStraitor.taken_objectives_by_type[/datum/traitor_objective/locate_weakpoint])
+ var/datum/uplink_handler/handler = weakpoint_objecitve.handler
+ if(handler.owner != user.mind)
+ continue
+
+ return weakpoint_objecitve
+
+/obj/item/grenade/c4/es8
+ name = "ES8 explosive charge"
+ desc = "A high-power explosive charge designed to create a shockwave in a structural weakpoint of the station."
+
+ icon_state = "plasticx40"
+ inhand_icon_state = "plasticx4"
+ worn_icon_state = "x4"
+
+ boom_sizes = list(3, 6, 9)
+
+ /// Reference to user's objective
+ var/datum/traitor_objective/locate_weakpoint/objective
+
+/obj/item/grenade/c4/es8/Destroy()
+ objective = null
+ return ..()
+
+/obj/item/grenade/c4/es8/afterattack(atom/movable/target, mob/user, flag)
+ if(!user.mind)
+ return
+
+ if(!user.mind.has_antag_datum(/datum/antagonist/traitor))
+ to_chat(user, span_warning("You can't seem to find a way to detonate the charge."))
+ return
+
+ for(var/datum/traitor_objective/locate_weakpoint/weakpoint_objecitve in SStraitor.taken_objectives_by_type[/datum/traitor_objective/locate_weakpoint])
+ var/datum/uplink_handler/handler = weakpoint_objecitve.handler
+ if(handler.owner != user.mind)
+ continue
+ objective = weakpoint_objecitve
+
+ if(!objective || objective.objective_state == OBJECTIVE_STATE_INACTIVE)
+ to_chat(user, span_warning("You don't think it would be wise to use [src]."))
+ return
+
+ var/area/target_area = get_area(target)
+ if (target_area.type != SStraitor.station_weakpoints[3])
+ var/area/weakpoint_area = SStraitor.station_weakpoints[3]
+ to_chat(user, span_warning("[src] can only be detonated in [initial(weakpoint_area.name)]."))
+ return
+
+ return ..()
+
+/obj/item/grenade/c4/es8/detonate(mob/living/lanced_by)
+ var/area/target_area = get_area(target)
+ if (target_area.type != SStraitor.station_weakpoints[3])
+ var/obj/item/grenade/c4/es8/new_bomb = new(target.drop_location())
+ new_bomb.balloon_alert_to_viewers("invalid location!")
+ target.cut_overlay(plastic_overlay, TRUE)
+ qdel(src)
+ return
+
+ if(!objective)
+ return
+
+ objective.create_shockwave(target.x, target.y, target.z)
+ return ..()
diff --git a/code/modules/antagonists/traitor/objectives/sleeper_protocol.dm b/code/modules/antagonists/traitor/objectives/sleeper_protocol.dm
index 92f30bdaa3a..2161346f22e 100644
--- a/code/modules/antagonists/traitor/objectives/sleeper_protocol.dm
+++ b/code/modules/antagonists/traitor/objectives/sleeper_protocol.dm
@@ -2,6 +2,7 @@
name = "Sleeper Protocol"
objectives = list(
/datum/traitor_objective/sleeper_protocol = 1,
+ /datum/traitor_objective/sleeper_protocol/everybody = 1,
)
@@ -17,12 +18,15 @@
JOB_MEDICAL_DOCTOR,
JOB_PARAMEDIC,
JOB_VIROLOGIST,
+ JOB_ROBOTICIST,
)
var/obj/item/disk/surgery/sleeper_protocol/disk
var/mob/living/current_registered_mob
+ var/inverted_limitation = FALSE
+
/datum/traitor_objective/sleeper_protocol/generate_ui_buttons(mob/user)
var/list/buttons = list()
if(!disk)
@@ -46,7 +50,9 @@
/datum/traitor_objective/sleeper_protocol/generate_objective(datum/mind/generating_for, list/possible_duplicates)
var/datum/job/job = generating_for.assigned_role
- if(!(job.title in limited_to))
+ if(!(job.title in limited_to) && !inverted_limitation)
+ return FALSE
+ if((job.title in limited_to) && inverted_limitation)
return FALSE
AddComponent(/datum/component/traitor_objective_mind_tracker, generating_for, \
signals = list(COMSIG_MOB_SURGERY_STEP_SUCCESS = .proc/on_surgery_success))
@@ -87,7 +93,7 @@
/datum/surgery_step/brainwash/sleeper_agent
time = 25 SECONDS
- var/list/possible_objectives = list(
+ var/static/list/possible_objectives = list(
"You love the Syndicate",
"Do not trust Nanotrasen",
"The Captain is a lizardperson",
@@ -111,3 +117,11 @@
if(!.)
return
target.gain_trauma(new /datum/brain_trauma/mild/phobia/conspiracies(), TRAUMA_RESILIENCE_LOBOTOMY)
+
+/datum/traitor_objective/sleeper_protocol/everybody //Much harder for non-med and non-robo
+
+ progression_minimum = 30 MINUTES
+ progression_reward = list(15 MINUTES, 20 MINUTES)
+ telecrystal_reward = list(2, 3)
+
+ inverted_limitation = TRUE
diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm
index f5fbf87b976..97c1631c9e1 100644
--- a/code/modules/cargo/supplypod.dm
+++ b/code/modules/cargo/supplypod.dm
@@ -78,6 +78,10 @@
bluespace = TRUE
explosionSize = list(0,0,1,2)
delays = list(POD_TRANSIT = 25, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30)
+ reversing = TRUE
+ stay_after_drop = TRUE
+ leavingSound = 'sound/effects/podwoosh.ogg'
+ reverse_option_list = list("Mobs"=TRUE,"Objects"=FALSE,"Anchored"=FALSE,"Underfloor"=FALSE,"Wallmounted"=FALSE,"Floors"=FALSE,"Walls"=FALSE, "Mecha"=FALSE)
/obj/structure/closet/supplypod/centcompod
style = STYLE_CENTCOM
diff --git a/icons/mob/inhands/misc/devices_lefthand.dmi b/icons/mob/inhands/misc/devices_lefthand.dmi
index 3c52a437283..55456a6e073 100644
Binary files a/icons/mob/inhands/misc/devices_lefthand.dmi and b/icons/mob/inhands/misc/devices_lefthand.dmi differ
diff --git a/icons/mob/inhands/misc/devices_righthand.dmi b/icons/mob/inhands/misc/devices_righthand.dmi
index e4c57d5ec55..a274e001eba 100644
Binary files a/icons/mob/inhands/misc/devices_righthand.dmi and b/icons/mob/inhands/misc/devices_righthand.dmi differ
diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi
index 96e6e0ce242..25f9fb08e47 100644
Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ
diff --git a/icons/obj/surgery.dmi b/icons/obj/surgery.dmi
index 24dbd2f70b7..410780b105f 100755
Binary files a/icons/obj/surgery.dmi and b/icons/obj/surgery.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 13fa2403358..e378b16fc79 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2319,7 +2319,9 @@
#include "code\modules\antagonists\traitor\objectives\destroy_item.dm"
#include "code\modules\antagonists\traitor\objectives\destroy_machinery.dm"
#include "code\modules\antagonists\traitor\objectives\hack_comm_console.dm"
+#include "code\modules\antagonists\traitor\objectives\kidnapping.dm"
#include "code\modules\antagonists\traitor\objectives\kill_pet.dm"
+#include "code\modules\antagonists\traitor\objectives\locate_weakpoint.dm"
#include "code\modules\antagonists\traitor\objectives\sleeper_protocol.dm"
#include "code\modules\antagonists\traitor\objectives\steal.dm"
#include "code\modules\antagonists\traitor\objectives\final_objective\battlecruiser.dm"