diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm
index 4eb05400649f..74ab51f6b384 100644
--- a/code/__DEFINES/hud.dm
+++ b/code/__DEFINES/hud.dm
@@ -201,6 +201,10 @@
#define ui_ghost_minigames "SOUTH: 6, CENTER+2:24"
#define ui_ghost_language_menu "SOUTH: 22, CENTER+3:8"
+//Team finder
+
+#define ui_team_finder "CENTER,CENTER"
+
//Blobbernauts
#define ui_blobbernaut_overmind_health "EAST-1:28,CENTER+0:19"
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index 45719f3af7f0..17391aec7423 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -97,6 +97,8 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
// subtypes can override this to force a specific UI style
var/ui_style
+ var/list/team_finder_arrows = list()
+
/datum/hud/New(mob/owner)
mymob = owner
@@ -209,6 +211,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
QDEL_NULL(module_store_icon)
QDEL_LIST(static_inventory)
+ QDEL_LIST(team_finder_arrows)
inv_slots.Cut()
action_intent = null
@@ -317,6 +320,8 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
screenmob.client.screen += hotkeybuttons
if(infodisplay.len)
screenmob.client.screen += infodisplay
+ if(team_finder_arrows.len)
+ screenmob.client.screen += team_finder_arrows
if(always_visible_inventory.len)
screenmob.client.screen += always_visible_inventory
@@ -329,12 +334,16 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
hud_shown = FALSE //Governs behavior of other procs
if(static_inventory.len)
screenmob.client.screen -= static_inventory
+ if(team_finder_arrows.len)
+ screenmob.client.screen += team_finder_arrows
if(toggleable_inventory.len)
screenmob.client.screen -= toggleable_inventory
if(hotkeybuttons.len)
screenmob.client.screen -= hotkeybuttons
if(infodisplay.len)
screenmob.client.screen += infodisplay
+ if(team_finder_arrows.len)
+ screenmob.client.screen -= team_finder_arrows
if(always_visible_inventory.len)
screenmob.client.screen += always_visible_inventory
diff --git a/monkestation/code/modules/new_antagonists/slasher/abilities/incorporealize.dm b/monkestation/code/modules/new_antagonists/slasher/abilities/incorporealize.dm
index f26799013121..54ad6a94e739 100644
--- a/monkestation/code/modules/new_antagonists/slasher/abilities/incorporealize.dm
+++ b/monkestation/code/modules/new_antagonists/slasher/abilities/incorporealize.dm
@@ -8,7 +8,6 @@
/datum/action/cooldown/slasher/incorporealize/PreActivate(atom/target)
. = ..()
- var/mob/living/carbon/human/human = owner
var/datum/antagonist/slasher/slasherdatum = owner.mind.has_antag_datum(/datum/antagonist/slasher)
if(slasherdatum && (slasherdatum.soul_punishment >= 2))
return FALSE
diff --git a/monkestation/code/modules/new_antagonists/slasher/abilities/recall_machette.dm b/monkestation/code/modules/new_antagonists/slasher/abilities/recall_machette.dm
index f70680399bd2..0cf7bb70fef7 100644
--- a/monkestation/code/modules/new_antagonists/slasher/abilities/recall_machette.dm
+++ b/monkestation/code/modules/new_antagonists/slasher/abilities/recall_machette.dm
@@ -11,8 +11,7 @@
/datum/action/cooldown/slasher/summon_machette/Destroy()
. = ..()
- qdel(stored_machette)
- stored_machette = null
+ QDEL_NULL(stored_machette)
/datum/action/cooldown/slasher/summon_machette/Activate(atom/target)
. = ..()
diff --git a/monkestation/code/modules/new_antagonists/slasher/abilities/soul_steal.dm b/monkestation/code/modules/new_antagonists/slasher/abilities/soul_steal.dm
index 6e5acf1c5b33..567d50a3a156 100644
--- a/monkestation/code/modules/new_antagonists/slasher/abilities/soul_steal.dm
+++ b/monkestation/code/modules/new_antagonists/slasher/abilities/soul_steal.dm
@@ -21,7 +21,7 @@
var/mob/living/carbon/human/human_owner = owner
var/datum/antagonist/slasher/slasherdatum = human_owner.mind.has_antag_datum(/datum/antagonist/slasher)
if(slasherdatum)
- if(last_soul_sucked + soul_digestion > world.time)
+ if(slasherdatum.last_soul_sucked + slasherdatum.soul_digestion > world.time)
to_chat(owner, span_boldwarning("You can feel your mind slipping, you feel as though bad things will happen if you absorb another soul so quickly!"))
per_soul_suck = 5 SECONDS
diff --git a/monkestation/code/modules/new_antagonists/slasher/abilities/stalk_target.dm b/monkestation/code/modules/new_antagonists/slasher/abilities/stalk_target.dm
new file mode 100644
index 000000000000..cb73aaa1b209
--- /dev/null
+++ b/monkestation/code/modules/new_antagonists/slasher/abilities/stalk_target.dm
@@ -0,0 +1,41 @@
+/mob/living
+ /// A weak reference to the team monitor component contained within the monitor holder, used for certain antagoists so they can track
+ var/datum/component/team_monitor/team_monitor
+ ///a reference to a stored /datum/component/tracking_beacon used by victims of antags
+ var/datum/component/tracking_beacon/tracking_beacon
+
+/datum/action/cooldown/slasher/stalk_target
+ name = "Stalk Target"
+ desc = "Get a target to stalk, standing near them for 3 minutes will rip their soul from their body. YOU MUST PROTECT THEM FROM HARM."
+
+ button_icon_state = "slasher_posses"
+
+ cooldown_time = 5 MINUTES
+
+/datum/action/cooldown/slasher/stalk_target/Activate(atom/target)
+ . = ..()
+ var/list/possible_targets = list()
+ for(var/datum/mind/possible_target as anything in get_crewmember_minds())
+ if(possible_target == owner.mind)
+ continue
+ if(!ishuman(possible_target.current))
+ continue
+ if(possible_target.current.stat == DEAD)
+ continue
+ possible_targets += possible_target.current
+
+ var/datum/antagonist/slasher/slasherdatum = owner.mind.has_antag_datum(/datum/antagonist/slasher)
+ if(slasherdatum && slasherdatum.stalked_human)
+ qdel(stalked_human.tracking_beacon)
+
+ var/mob/living/living_target = pick(possible_targets)
+ var/mob/living/carbon/human/owner_human = owner
+ if(!owner_human.team_monitor)
+ owner_human.tracking_beacon = owner_human.AddComponent(/datum/component/tracking_beacon, "slasher", null, null, TRUE, "#00660e")
+ owner_human.team_monitor = owner_human.AddComponent(/datum/component/team_monitor, "slasher", null, owner_human.tracking_beacon)
+
+ living_target.tracking_beacon = living_target.AddComponent(/datum/component/tracking_beacon, "slasher", null, null, TRUE, "#660000")
+ if(slasherdatum)
+ slasherdatum.stalked_human = living_target
+ owner_human.team_monitor.add_to_tracking_network(living_target.tracking_beacon)
+ owner_human.team_monitor.show_hud(owner_human)
diff --git a/monkestation/code/modules/new_antagonists/slasher/components/team_monitor.dm b/monkestation/code/modules/new_antagonists/slasher/components/team_monitor.dm
new file mode 100644
index 000000000000..0b36588a0e70
--- /dev/null
+++ b/monkestation/code/modules/new_antagonists/slasher/components/team_monitor.dm
@@ -0,0 +1,603 @@
+#define ALT_APPEARENCE_ID "team_monitor"
+
+//==================
+// Helpers
+//==================
+
+//A list that tracks everything that should be tracked by team monitors
+//Assoc list:
+// Key = Frequency
+// Value = Components
+GLOBAL_LIST_EMPTY(tracker_huds)
+GLOBAL_LIST_EMPTY(tracker_beacons)
+
+//Gets the first free team element, useful for creating new teams
+//Special key is for what kind of team frequency it should be
+//Everything that has a team monitor can be configured to change what frequency it tracks on
+//The special key can be used to make keys like synd5 or synd83 to prevent centcom tracking syndies.
+/proc/get_free_team_frequency(special_key = "")
+ var/sanity = 5
+ //5 attempts to find a free team element, should never get that far
+ while(sanity > 0)
+ sanity --
+ var/random_id = rand(1, 999)
+ var/key = "[random_id]"
+ if(!GLOB.tracker_beacons.Find("[special_key][key]"))
+ return key
+ //Return something anyways
+ var/random_id = rand(1, 999)
+ var/key = "[random_id]"
+ return key
+
+//Adds a new tracking hud
+/proc/add_tracker_hud(frequency_added, datum/component/component_added)
+ if(!frequency_added)
+ return
+ if(islist(GLOB.tracker_huds[frequency_added]))
+ GLOB.tracker_huds[frequency_added] |= component_added
+ else
+ GLOB.tracker_huds[frequency_added] = list(component_added)
+
+//Adds a new tracking beacon
+/proc/add_tracker_beacon(frequency_added, datum/component/component_added)
+ if(!frequency_added)
+ return
+ if(islist(GLOB.tracker_beacons[frequency_added]))
+ GLOB.tracker_beacons[frequency_added] |= component_added
+ else
+ GLOB.tracker_beacons[frequency_added] = list(component_added)
+
+/proc/get_all_beacons_on_frequency(frequency, base_frequency)
+ if(!frequency)
+ return GLOB.tracker_beacons["[base_frequency]-GLOB"]
+ var/list/found_beacons = list()
+ if(islist(GLOB.tracker_beacons[frequency]))
+ found_beacons.Add(GLOB.tracker_beacons[frequency])
+ if(islist(GLOB.tracker_beacons["[base_frequency]-GLOB"]))
+ found_beacons.Add(GLOB.tracker_beacons["[base_frequency]-GLOB"])
+ return found_beacons
+
+/proc/get_all_watchers_on_frequency(frequency, team_key = "", global_freq = FALSE)
+ if(global_freq)
+ . = list()
+ for(var/tracker_freq in GLOB.tracker_huds)
+ for(var/datum/component/team_monitor/TM as() in GLOB.tracker_huds[tracker_freq])
+ if(TM.team_freq_key == team_key)
+ . += TM
+ else
+ return GLOB.tracker_huds[frequency]
+
+//==================
+// Component
+// - HUD COMPONENT
+// - HANDLES POINTING TOWARDS TRACKED BEACONS
+//==================
+
+//The component that handles tracking atoms
+/datum/component/team_monitor
+ /// The frequency of the team signals we are trackings
+ /// Key <-- cannot be changed
+ var/team_freq_key = "debug"
+ /// Final compiled: Consists of key then numbers between 1 and 999
+ var/team_frequency = ""
+ /// The atoms we are actually tracking
+ /// Key = Beacon component
+ /// Value = image
+ var/list/tracking = list()
+ /// Who are we updating for
+ var/mob/updating = null
+ /// Distance from center
+ /// Probably in pixels or something idk
+ var/distance = 20
+ /// Should we display the hud in the firstplace
+ var/hud_visible = TRUE
+ /// The attached beacon: Ignore this one
+ var/datum/component/tracking_beacon/attached_beacon
+ /// If we can track beacons within the same zgroup (e.g. on a multiz station)
+ var/multiz = TRUE
+
+/datum/component/team_monitor/Initialize(frequency_key, frequency, _attached_beacon, _multiz = TRUE)
+ multiz = _multiz
+ team_freq_key = frequency_key
+ if(frequency)
+ team_frequency = "[frequency_key][frequency]"
+ else
+ team_frequency = null
+
+ attached_beacon = _attached_beacon
+
+ get_matching_beacons()
+ add_tracker_hud(team_frequency, src)
+
+/datum/component/team_monitor/Destroy(force, silent)
+ if(team_frequency)
+ GLOB.tracker_huds[team_frequency] -= src
+
+ //Stop processing
+ STOP_PROCESSING(SSprocessing, src)
+
+ //Remove the HUD from the equipped mob
+ if(updating)
+ hide_hud(updating)
+
+ //Dispose
+ if(attached_beacon)
+ if(attached_beacon.attached_monitor == src)
+ attached_beacon.attached_monitor = null
+ attached_beacon = null
+
+ . = ..()
+
+//Gets the active trackers for when the team_monitor component
+//is initialized while other trackers are already active.
+/datum/component/team_monitor/proc/get_matching_beacons()
+ for(var/datum/component/tracking_beacon/beacon as() in get_all_beacons_on_frequency(team_frequency, team_freq_key))
+ if(beacon != attached_beacon && (beacon.updating || beacon.always_update))
+ add_to_tracking_network(beacon)
+
+//===========
+// Handles the parent being moved and updates the direction of the arrows.
+//===========
+
+/datum/component/team_monitor/process()
+ update_all_directions()
+
+//When the parent is removed, we need to update our arrows
+//Also if we are visible update the arrows of anything tracking us
+/datum/component/team_monitor/proc/parent_moved()
+ SIGNAL_HANDLER
+
+ //Update our alt appearances
+ update_all_directions()
+
+//Updates the direction of the arrows for all atoms we are tracking
+/datum/component/team_monitor/proc/update_all_directions()
+ if(!updating)
+ return
+ for(var/datum/component/tracking_beacon/beacon as() in tracking)
+ update_atom_dir(beacon)
+
+//Update the arrow towards another atom
+/datum/component/team_monitor/proc/update_atom_dir(datum/component/tracking_beacon/beacon)
+ if(!updating || !updating.hud_used || !beacon || !beacon.visible)
+ return
+ var/atom/movable/screen/arrow/screen = tracking[beacon]
+ var/turf/target_turf = get_turf(beacon.parent)
+ var/turf/parent_turf = get_turf(parent)
+ var/share_z = target_turf.z == parent_turf.z
+ if((!share_z && (!multiz)) || target_turf == parent_turf)
+ if(screen)
+ //Remove the screen
+ updating.hud_used.team_finder_arrows -= screen
+ qdel(screen)
+ tracking[beacon] = null
+ //Update their hud
+ updating.hud_used.show_hud(updating.hud_used.hud_version, updating)
+ return
+ if(!screen)
+ //Create the screen
+ screen = new
+ screen.alpha = 240
+ if(multiz && !share_z && screen.color != beacon.z_diff_colour)
+ screen.color = beacon.z_diff_colour
+ else if(screen.color != beacon.colour)
+ screen.color = beacon.colour
+ screen.hud = updating.hud_used
+ updating.hud_used.team_finder_arrows += screen
+ tracking[beacon] = screen
+ //Update their hud
+ updating.hud_used.show_hud(updating.hud_used.hud_version, updating)
+ if(multiz && !share_z && screen.color != beacon.z_diff_colour)
+ screen.color = beacon.z_diff_colour
+ else if(screen.color != beacon.colour)
+ screen.color = beacon.colour
+ var/matrix/rotationMatrix = matrix()
+ rotationMatrix.Scale(1.5)
+ rotationMatrix.Translate(0, -distance)
+ rotationMatrix.Turn(get_angle(target_turf, parent_turf))
+ animate(screen, transform = rotationMatrix, time = 2)
+
+//===========
+// Handles hiding / showing the hud when equipped
+//===========
+
+/datum/component/team_monitor/proc/show_hud(mob/target)
+ //Our hud is disabled
+ if(!hud_visible || !target)
+ return
+ updating = target
+ //Start processing to update in weird situations
+ START_PROCESSING(SSprocessing, src)
+ //Register parent signal
+ RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(parent_moved))
+ //Mob doesnt have a hud, dont add hud arrows
+ if(!target.hud_used)
+ return
+ for(var/datum/component/tracking_beacon/key in tracking)
+ if(!key.visible) // calling show_hud should not show hidden beacons
+ continue
+ var/atom/movable/screen/arrow/arrow = new
+ arrow.alpha = 240
+ var/turf/target_turf = get_turf(key.parent)
+ var/turf/parent_turf = get_turf(parent)
+ if(multiz && target_turf.z != parent_turf.z && arrow.color != key.z_diff_colour)
+ arrow.color = key.z_diff_colour
+ else if(arrow.color != key.colour)
+ arrow.color = key.colour
+ arrow.hud = target.hud_used
+ target.hud_used.team_finder_arrows += arrow
+ tracking[key] = arrow
+ //Update their hud
+ target.hud_used.show_hud(target.hud_used.hud_version, target)
+ update_all_directions()
+
+/datum/component/team_monitor/proc/hide_hud(mob/target)
+ updating = null
+ //Stop processing
+ STOP_PROCESSING(SSprocessing, src)
+ if(!target)
+ return
+ //UnRegister parent signal
+ UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
+ //Remove our arrows
+ for(var/key in tracking)
+ var/atom/movable/screen/arrow = tracking[key]
+ if(!arrow)
+ continue
+ if(target.hud_used)
+ target.hud_used.team_finder_arrows -= arrow
+ qdel(arrow)
+ tracking[key] = null
+ //Update their hud
+ if(target.hud_used)
+ target.hud_used.show_hud(target.hud_used.hud_version, target)
+
+//===========
+// Handles user interaction
+// - Disabling hud transmission
+// - Disabling hud view
+// - Changing transmission frequency
+//===========
+
+/datum/component/team_monitor/proc/toggle_hud(new_hud_status, mob/user)
+ hud_visible = new_hud_status
+ if(hud_visible && !updating)
+ show_hud(user)
+ else if(!hud_visible)
+ hide_hud(user)
+
+/datum/component/team_monitor/proc/change_frequency(mob/user)
+ //Get new frequency
+ var/new_freq = input(user, "Enter a new frequency (1 - 999):", "Frequency Change", 1) as num|null
+ if(!new_freq)
+ to_chat(user, "Invalid frequency. Encrypted tracking HUD disabled.")
+ return
+ if(new_freq < 1 || new_freq > 999)
+ to_chat(user, "Frequency is out of range. Must be between 1 and 999.")
+ return
+ set_frequency(new_freq)
+ to_chat(user, "Tracking HUD now scanning on frequency [team_frequency].")
+ //Set frequency of the linked beacon
+ if(attached_beacon)
+ attached_beacon.set_frequency(new_freq)
+
+/datum/component/team_monitor/proc/set_frequency(new_frequency)
+ var/hud_on = hud_visible
+ var/mob/user = updating
+ //Remove tracking from old frequency
+ if(team_frequency)
+ if(updating)
+ toggle_hud(FALSE, updating)
+ //Remove from the global frequency
+ GLOB.tracker_huds[team_frequency] -= src
+ //Clear tracking
+ tracking.Cut()
+ team_frequency = "[team_freq_key][new_frequency]"
+ //Add tracking to new frequency
+ if(!team_frequency)
+ return
+ //Adds our tracking component to the global list of trackers
+ add_tracker_hud(team_frequency, src)
+ //Gets the other trackers on our frequency
+ get_matching_beacons()
+ //Show hud if needed
+ if(user)
+ toggle_hud(hud_on, user)
+
+//Adds a new atom to the tracking monitor, will create a hud element that tracks them
+//TODO: Add the screen if already equipped
+//Should be the only way atoms are added to the tracking list
+/datum/component/team_monitor/proc/add_to_tracking_network(datum/component/tracking_beacon/beacon)
+ if(beacon != attached_beacon)
+ if(updating?.hud_used)
+ var/atom/movable/screen/arrow/arrow = new
+ arrow.alpha = 240
+ var/turf/target_turf = get_turf(beacon.parent)
+ var/turf/parent_turf = get_turf(parent)
+ if(multiz && target_turf.z != parent_turf.z && arrow.color != beacon.z_diff_colour)
+ arrow.color = beacon.z_diff_colour
+ else if(arrow.color != beacon.colour)
+ arrow.color = beacon.colour
+ arrow.hud = updating.hud_used
+ updating.hud_used.team_finder_arrows += arrow
+ tracking[beacon] = arrow
+ //Update arrow direction
+ update_atom_dir(beacon)
+ //Update their hud
+ updating.hud_used.show_hud(updating.hud_used.hud_version, updating)
+ else
+ tracking[beacon] = null
+
+// ============
+// Worn version, hides when dequipped
+// ============
+
+/datum/component/team_monitor/worn/Initialize(frequency_key, frequency, _attached_beacon)
+ var/obj/item/clothing/item = parent
+ if(!istype(item))
+ return COMPONENT_INCOMPATIBLE
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(parent_equipped))
+ RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(parent_dequpped))
+ ..()
+
+//===========
+// Handles being equipped / dequipped
+//===========
+
+//The parent equipped an item with a team_monitor, check if its in the right slot and apply the hud
+//Also needs to enable other trackers pointers towards us
+/datum/component/team_monitor/worn/proc/parent_equipped(datum/source, mob/equipper, slot)
+ SIGNAL_HANDLER
+
+ var/obj/item/clothing/item = parent
+ if(!istype(item))
+ return
+ if(item.slot_flags & slot) //Was equipped to a valid slot for this item?
+ show_hud(equipper)
+ else
+ hide_hud(equipper)
+
+//Disable our hud
+//Disable the pointers to us
+/datum/component/team_monitor/worn/proc/parent_dequpped(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ hide_hud(user)
+
+/datum/component/team_monitor/worn/Destroy(force, silent)
+ //Unregister signals
+ if(parent)
+ UnregisterSignal(parent, COMSIG_ITEM_EQUIPPED)
+ UnregisterSignal(parent, COMSIG_ITEM_DROPPED)
+ return ..()
+
+//==================
+// Component
+// - TRACKER COMPONENT
+// - HANDLES UPDATING TRACKERS WHEN MOVED
+//==================
+
+/datum/component/tracking_beacon
+ /// The frequency of the team signals we are trackings
+ /// Key <-- cannot be changed
+ var/team_freq_key = "debug"
+ /// Final compiled: Consists of key then numbers between 1 and 999
+ var/team_frequency = ""
+ /// Are we visible to other trackers?
+ var/visible = TRUE
+ /// Our colour
+ var/colour = "#FFFFFF"
+ /// Colour when on a different z level
+ var/z_diff_colour = "#808080"
+ /// Who are we updating for
+ var/mob/updating = null
+ /// Do we have an attached monitor?
+ var/datum/component/team_monitor/attached_monitor
+ /// Should we update when not equipped?
+ var/always_update = FALSE
+ /// Global signal?
+ var/global_signal = FALSE
+
+/datum/component/tracking_beacon/Initialize(_frequency_key, _frequency, _attached_monitor, _visible = TRUE, _colour = "#ffffff", _global = FALSE, _always_update = FALSE, _z_diff_colour = "#808080")
+ . = ..()
+
+ //Set vars
+ colour = _colour
+ z_diff_colour = _z_diff_colour
+ attached_monitor = _attached_monitor
+ always_update = _always_update
+ global_signal = _global
+
+ //Set the frequency we are transmitting on
+ team_freq_key = _frequency_key
+ if(_global)
+ team_frequency = "[_frequency_key]-GLOB"
+ else if(_frequency)
+ team_frequency = "[_frequency_key][_frequency]"
+ else
+ team_frequency = null
+
+ //Add ourselves to the tracking network
+ add_tracker_beacon(team_frequency, src)
+
+ //Register tracking signal
+ if(always_update)
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(update_position))
+ else
+ //Reigster equipping signals
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(parent_equipped))
+ RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(parent_dequpped))
+
+ //Set our visibility on the tracking network
+ toggle_visibility(_visible)
+
+/datum/component/tracking_beacon/Destroy(force, silent)
+ //Unregister signals
+ if(parent)
+ //Register tracking signal
+ if(always_update)
+ UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)
+ else
+ UnregisterSignal(parent, COMSIG_ITEM_EQUIPPED)
+ UnregisterSignal(parent, COMSIG_ITEM_DROPPED)
+
+ //Unregister movement signal
+ if(updating)
+ UnregisterSignal(updating, COMSIG_MOVABLE_MOVED)
+
+ //Goodbye, it was a good life
+ remove_from_huds()
+
+ //Remove from the global network
+ if(team_frequency)
+ GLOB.tracker_beacons[team_frequency] -= src
+
+ if(attached_monitor?.attached_beacon == src)
+ attached_monitor.attached_beacon = null
+ attached_monitor = null
+
+ . = ..()
+
+//===========
+// Equip/Dequip transmission handling
+//===========
+
+//The parent equipped an item with a team_monitor, check if its in the right slot and apply the hud
+//Also needs to enable other trackers pointers towards us
+/datum/component/tracking_beacon/proc/parent_equipped(datum/source, mob/equipper, slot)
+ SIGNAL_HANDLER
+
+ var/obj/item/clothing/item = parent
+ if(!istype(item))
+ return
+ if(item.slot_flags & slot) //Was equipped to a valid slot for this item?
+ updating = equipper
+ toggle_visibility(TRUE)
+ RegisterSignal(updating, COMSIG_MOVABLE_MOVED, PROC_REF(update_position))
+ else
+ toggle_visibility(FALSE)
+ if(updating)
+ UnregisterSignal(updating, COMSIG_MOVABLE_MOVED)
+ updating = null
+
+//Disable our hud
+//Disable the pointers to us
+/datum/component/tracking_beacon/proc/parent_dequpped(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ toggle_visibility(FALSE)
+ if(updating)
+ UnregisterSignal(updating, COMSIG_MOVABLE_MOVED)
+ updating = null
+
+//===========
+// Visibility Handling
+//===========
+
+//Toggle visibility
+//If visibility is disabled we will hide ourselves from others
+/datum/component/tracking_beacon/proc/toggle_visibility(new_vis)
+ visible = new_vis
+ //If we are updating toggle our visibility
+ if((updating || always_update) && visible)
+ add_to_huds()
+ else
+ remove_from_huds()
+
+//===========
+// Position Updating
+//===========
+
+/datum/component/tracking_beacon/proc/update_position()
+ SIGNAL_HANDLER
+
+ //Update everyone tracking us
+ if(!visible)
+ return
+ if(!team_frequency)
+ return
+ for(var/datum/component/team_monitor/TM as() in get_all_watchers_on_frequency(team_frequency, team_freq_key, global_signal))
+ if(TM != attached_monitor)
+ TM.update_atom_dir(src)
+
+//===========
+// Showing on huds
+//===========
+
+//Remove ourselves from other tracking components
+/datum/component/tracking_beacon/proc/remove_from_huds()
+ if(!team_frequency)
+ return
+ for(var/datum/component/team_monitor/team_monitor as() in get_all_watchers_on_frequency(team_frequency, team_freq_key, global_signal))
+ //Remove ourselves from the tracking list
+ var/atom/movable/screen/arrow = team_monitor.tracking[src]
+ team_monitor.tracking.Remove(src)
+ //Delete the arrow pointing to use
+ if(!arrow)
+ continue
+ if(team_monitor.updating?.hud_used)
+ team_monitor.updating.hud_used.team_finder_arrows -= arrow
+ //Update their hud
+ team_monitor.updating.hud_used.show_hud(team_monitor.updating.hud_used.hud_version, team_monitor.updating)
+ qdel(arrow)
+
+//Add ourselves to other tracking components
+/datum/component/tracking_beacon/proc/add_to_huds()
+ //If we are invisibile, dont bother
+ if(!visible)
+ return
+ //Find other trackers and add ourselves to their tracking network
+ if(!team_frequency)
+ return
+ for(var/datum/component/team_monitor/team_monitor as() in get_all_watchers_on_frequency(team_frequency, team_freq_key, global_signal))
+ if(team_monitor != attached_monitor)
+ team_monitor.add_to_tracking_network(src)
+
+//===========
+// Handles user interaction
+// - Disabling hud transmission
+// - Disabling hud view
+// - Changing transmission frequency
+//===========
+
+/datum/component/tracking_beacon/proc/change_frequency(mob/user)
+ //Get new frequency
+ var/new_freq = input(user, "Enter a new frequency (1 - 999):", "Frequency Change", 1) as num|null
+ if(!new_freq)
+ to_chat(user, "Invalid frequency. Encrypted tracking beacon disabled.")
+ return
+ if(new_freq < 1 || new_freq > 999)
+ to_chat(user, "Frequency is out of range. Must be between 1 and 999.")
+ return
+ set_frequency(new_freq)
+ to_chat(user, "Tracking HUD now transmitting on frequency [team_frequency].")
+ //Set frequency of the linked tracker
+ if(attached_monitor)
+ attached_monitor.set_frequency(new_freq)
+
+/datum/component/tracking_beacon/proc/set_frequency(new_frequency)
+ //Remove tracking from old frequency
+ if(team_frequency)
+ //Disable the beacon on other trackers
+ toggle_visibility(FALSE)
+ //Remove from the global frequency
+ GLOB.tracker_beacons[team_frequency] -= src
+ team_frequency = "[team_freq_key][new_frequency]"
+ //Add tracking to new frequency
+ if(!team_frequency)
+ return
+ //Adds our tracking component to the global list of trackers
+ add_tracker_beacon(team_frequency, src)
+ //Set our visibility on the tracking network
+ toggle_visibility(visible)
+
+//=======
+// Generic Arrow, No special effects
+//=======
+
+/atom/movable/screen/arrow
+ icon = 'monkestation/icons/mob/hud.dmi'
+ icon_state = "hud_arrow"
+ screen_loc = ui_team_finder
+
+#undef ALT_APPEARENCE_ID
diff --git a/monkestation/code/modules/new_antagonists/slasher/slasher_datum.dm b/monkestation/code/modules/new_antagonists/slasher/slasher_datum.dm
index 505fc6c5504e..f1f039f1eaad 100644
--- a/monkestation/code/modules/new_antagonists/slasher/slasher_datum.dm
+++ b/monkestation/code/modules/new_antagonists/slasher/slasher_datum.dm
@@ -36,6 +36,10 @@
var/list/mobs_with_fullscreens = list()
///this is needed because it double fires sometimes before finishing
var/is_hudchecking = FALSE
+ /// the mob we are stalking
+ var/mob/living/carbon/human/stalked_human
+ /// how close we are in % to finishing stalking
+ var/stalk_precent = 0
/datum/antagonist/slasher/apply_innate_effects(mob/living/mob_override)
. = ..()
@@ -63,6 +67,8 @@
regenerate.Grant(current_mob)
var/datum/action/cooldown/slasher/terror/terror = new
terror.Grant(current_mob)
+ var/datum/action/cooldown/slasher/stalk_target/stalk_target = new
+ stalk_target.Grant(current_mob)
var/mob/living/carbon/human/human = current_mob
human.equipOutfit(/datum/outfit/slasher)
@@ -84,6 +90,16 @@
continue
human.playsound_local(human, 'sound/health/slowbeat.ogg', 40, FALSE, channel = CHANNEL_HEARTBEAT, use_reverb = FALSE)
+ if(stalked_human)
+ for(var/mob/living/carbon/human in view(7, source))
+ if(stalked_human != human)
+ continue
+ if(stalked_human.stat == DEAD)
+ failed_stalking()
+ stalk_precent += (1 / 1.8)
+ if(stalk_precent >= 100)
+ finish_stalking()
+
if(!is_hudchecking)
is_hudchecking = TRUE
var/list/starting_humans = list()
@@ -122,3 +138,19 @@
var/turf/turf = get_turf(human)
var/list/blood_drop = list(human.get_blood_id() = 10)
turf.add_liquid_list(blood_drop, FALSE, 300)
+
+/datum/antagonist/slasher/proc/finish_stalking()
+ to_chat(owner, span_boldwarning("You have finished spooking your victim, and have harvested part of their soul!"))
+ if(linked_machette)
+ linked_machette.force += 2.5
+ linked_machette.throwforce += 2.5
+ qdel(stalked_human.tracking_beacon)
+ stalked_human = null
+
+/datum/antagonist/slasher/proc/failed_stalking()
+ to_chat(owner, span_boldwarning("You let your victim be taken before it was time!"))
+ if(linked_machette)
+ linked_machette.force -= 5
+ linked_machette.throwforce -= 5
+ qdel(stalked_human.tracking_beacon)
+ stalked_human = null
diff --git a/monkestation/icons/mob/hud.dmi b/monkestation/icons/mob/hud.dmi
new file mode 100644
index 000000000000..70cc33f1ee1c
Binary files /dev/null and b/monkestation/icons/mob/hud.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index dc63d3d14b7a..eee0abb2ee07 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -6032,7 +6032,9 @@
#include "monkestation\code\modules\new_antagonists\slasher\abilities\recall_machette.dm"
#include "monkestation\code\modules\new_antagonists\slasher\abilities\slasher_regenerate.dm"
#include "monkestation\code\modules\new_antagonists\slasher\abilities\soul_steal.dm"
+#include "monkestation\code\modules\new_antagonists\slasher\abilities\stalk_target.dm"
#include "monkestation\code\modules\new_antagonists\slasher\abilities\terror.dm"
+#include "monkestation\code\modules\new_antagonists\slasher\components\team_monitor.dm"
#include "monkestation\code\modules\new_antagonists\slasher\slasher_outfit\slasher_footwear.dm"
#include "monkestation\code\modules\new_antagonists\slasher\slasher_outfit\slasher_headgear.dm"
#include "monkestation\code\modules\new_antagonists\slasher\slasher_outfit\slasher_middlewear.dm"