diff --git a/code/__DEFINES/pinpointers.dm b/code/__DEFINES/pinpointers.dm
new file mode 100644
index 000000000000..80403e54de7f
--- /dev/null
+++ b/code/__DEFINES/pinpointers.dm
@@ -0,0 +1,7 @@
+//I would rather have these in pinpointer.dm, but Malf_Modules.dm is loaded before that file so they need to be here.
+#define TRACK_NUKE_DISK 1 //We track the nuclear authentication disk, either to protect it or steal it
+#define TRACK_MALF_AI 2 //We track the malfunctioning AI, so we can prevent it from blowing us all up
+#define TRACK_INFILTRATOR 3 //We track the Syndicate infiltrator, so we can get back to ship when the nuke's armed
+#define TRACK_OPERATIVES 4 //We track the closest operative, so we can regroup when we need to
+#define TRACK_ATOM 5 //We track a specified atom, so admins can make us function for events
+#define TRACK_COORDINATES 6 //We point towards the specified coordinates on our z-level, so we can navigate
diff --git a/code/game/gamemodes/malfunction/Malf_Modules.dm b/code/game/gamemodes/malfunction/Malf_Modules.dm
index bae0c8eb6481..76a6de59b026 100644
--- a/code/game/gamemodes/malfunction/Malf_Modules.dm
+++ b/code/game/gamemodes/malfunction/Malf_Modules.dm
@@ -40,10 +40,8 @@
doomsday_device = DOOM
doomsday_device.start()
verbs -= /mob/living/silicon/ai/proc/nuke_station
- for(var/obj/item/weapon/pinpointer/point in pinpointer_list)
- for(var/mob/living/silicon/ai/A in ai_list)
- if((A.stat != DEAD) && A.nuking)
- point.the_disk = A //The pinpointer now tracks the AI core
+ for(var/obj/item/weapon/pinpointer/P in pinpointer_list)
+ P.switch_mode_to(TRACK_MALF_AI) //Pinpointers start tracking the AI wherever it goes
/obj/machinery/doomsday_device
icon = 'icons/obj/machines/nuke_terminal.dmi'
diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm
index 440d3ac1da61..5f0b9bec7c15 100644
--- a/code/game/gamemodes/nuclear/nuclear.dm
+++ b/code/game/gamemodes/nuclear/nuclear.dm
@@ -296,7 +296,7 @@
gloves = /obj/item/clothing/gloves/combat
back = /obj/item/weapon/storage/backpack
ears = /obj/item/device/radio/headset/syndicate/alt
- l_pocket = /obj/item/weapon/pinpointer/nukeop
+ l_pocket = /obj/item/weapon/pinpointer/syndicate
id = /obj/item/weapon/card/id/syndicate
belt = /obj/item/weapon/gun/projectile/automatic/pistol
backpack_contents = list(/obj/item/weapon/storage/box/syndie=1)
diff --git a/code/game/gamemodes/nuclear/nuclearbomb.dm b/code/game/gamemodes/nuclear/nuclearbomb.dm
index 14418fd66faf..f1f35ccf1d6a 100644
--- a/code/game/gamemodes/nuclear/nuclearbomb.dm
+++ b/code/game/gamemodes/nuclear/nuclearbomb.dm
@@ -358,6 +358,9 @@ var/bomb_set
if(safety)
if(timing)
set_security_level(previous_level)
+ for(var/obj/item/weapon/pinpointer/syndicate/S in pinpointer_list)
+ S.switch_mode_to(initial(S.mode))
+ S.nuke_warning = FALSE
timing = FALSE
bomb_set = TRUE
detonation_timer = null
@@ -374,6 +377,8 @@ var/bomb_set
bomb_set = TRUE
set_security_level("delta")
detonation_timer = world.time + (timer_set * 10)
+ for(var/obj/item/weapon/pinpointer/syndicate/S in pinpointer_list)
+ S.switch_mode_to(TRACK_INFILTRATOR)
countdown.start()
else
bomb_set = FALSE
diff --git a/code/game/gamemodes/nuclear/pinpointer.dm b/code/game/gamemodes/nuclear/pinpointer.dm
index 6878855b9336..dc35428809de 100644
--- a/code/game/gamemodes/nuclear/pinpointer.dm
+++ b/code/game/gamemodes/nuclear/pinpointer.dm
@@ -1,5 +1,7 @@
+//Pinpointers are used to track atoms from a distance as long as they're on the same z-level. The captain and nuke ops have ones that track the nuclear authentication disk.
/obj/item/weapon/pinpointer
name = "pinpointer"
+ desc = "A handheld tracking device that locks onto certain signals."
icon = 'icons/obj/device.dmi'
icon_state = "pinoff"
flags = CONDUCT
@@ -8,290 +10,162 @@
item_state = "electronic"
throw_speed = 3
throw_range = 7
- materials = list(MAT_METAL=500)
- var/obj/item/weapon/disk/nuclear/the_disk = null
- var/active = 0
+ materials = list(MAT_METAL = 500, MAT_GLASS = 250)
+ var/active = FALSE
+ var/atom/movable/target = null //The thing we're searching for
+ var/atom/movable/constant_target = null //The thing we're always focused on, if we're in the right mode
+ var/target_x = 0 //The target coordinates if we're tracking those
+ var/target_y = 0
+ var/nuke_warning = FALSE // If we've set off a miniature alarm about an armed nuke
+ var/mode = TRACK_NUKE_DISK //What are we looking for?
/obj/item/weapon/pinpointer/New()
..()
pinpointer_list += src
/obj/item/weapon/pinpointer/Destroy()
- active = 0
- pinpointer_list -= src
+ STOP_PROCESSING(SSfastprocess, src)
return ..()
-/obj/item/weapon/pinpointer/attack_self()
- if(!active)
- active = 1
- workdisk()
- usr << "You activate the pinpointer."
+/obj/item/weapon/pinpointer/attack_self(mob/living/user)
+ active = !active
+ user.visible_message("[user] [active ? "" : "de"]activates their pinpointer.", "You [active ? "" : "de"]activate your pinpointer.")
+ playsound(user, 'sound/items/Screwdriver2.ogg', 50, 1)
+ icon_state = "pin[active ? "onnull" : "off"]"
+ if(active)
+ START_PROCESSING(SSfastprocess, src)
else
- active = 0
- icon_state = "pinoff"
- usr << "You deactivate the pinpointer."
+ target = null //Restarting the pinpointer forces a target reset
+ STOP_PROCESSING(SSfastprocess, src)
-/obj/item/weapon/pinpointer/proc/scandisk()
- if(!the_disk)
- the_disk = locate()
-
-/obj/item/weapon/pinpointer/proc/point_at(atom/target, spawnself = 1)
- if(!active)
- return
- if(!target)
- icon_state = "pinonnull"
- return
-
- var/turf/T = get_turf(target)
- var/turf/L = get_turf(src)
-
- if(T.z != L.z)
- icon_state = "pinonnull"
- else
- setDir(get_dir(L, T))
- switch(get_dist(L, T))
- if(-1)
- icon_state = "pinondirect"
- if(1 to 8)
- icon_state = "pinonclose"
- if(9 to 16)
- icon_state = "pinonmedium"
- if(16 to INFINITY)
- icon_state = "pinonfar"
- if(spawnself)
- spawn(5)
- .()
-
-/obj/item/weapon/pinpointer/proc/workdisk()
- scandisk()
- point_at(the_disk, 0)
- spawn(5)
- .()
+/obj/item/weapon/pinpointer/attackby(obj/item/I, mob/living/user, params)
+ if(mode != TRACK_ATOM)
+ return ..()
+ user.visible_message("[user] tunes [src] to [I].", "You fine-tune [src]'s tracking to track [I].")
+ playsound(src, 'sound/machines/click.ogg', 50, 1)
+ constant_target = I
/obj/item/weapon/pinpointer/examine(mob/user)
..()
+ var/msg = "Its tracking indicator reads "
+ switch(mode)
+ if(TRACK_NUKE_DISK)
+ msg += "\"nuclear_disk\"."
+ if(TRACK_MALF_AI)
+ msg += "\"01000001 01001001\"."
+ if(TRACK_INFILTRATOR)
+ msg += "\"vasvygengbefuvc\"."
+ if(TRACK_OPERATIVES)
+ msg += "\"[target ? "Operative [target]" : "friends"]\"."
+ if(TRACK_ATOM)
+ msg += "\"[initial(constant_target.name)]\"."
+ if(TRACK_COORDINATES)
+ msg += "\"([target_x], [target_y])\"."
+ else
+ msg = "Its tracking indicator is blank."
+ user << msg
for(var/obj/machinery/nuclearbomb/bomb in machines)
if(bomb.timing)
user << "Extreme danger. Arming signal detected. Time remaining: [bomb.get_time_left()]"
-
-/obj/item/weapon/pinpointer/advpinpointer
- name = "advanced pinpointer"
- icon = 'icons/obj/device.dmi'
- desc = "A larger version of the normal pinpointer, this unit features a helpful quantum entanglement detection system to locate various objects that do not broadcast a locator signal."
- var/mode = 0 // Mode 0 locates disk, mode 1 locates coordinates.
- var/turf/location = null
- var/obj/target = null
-
-/obj/item/weapon/pinpointer/advpinpointer/attack_self()
+/obj/item/weapon/pinpointer/process()
if(!active)
- active = 1
- if(mode == 0)
- workdisk()
- if(mode == 1)
- point_at(location)
- if(mode == 2)
- point_at(target)
- usr << "You activate the pinpointer."
- else
- active = 0
- icon_state = "pinoff"
- usr << "You deactivate the pinpointer."
-
-
-/obj/item/weapon/pinpointer/advpinpointer/verb/toggle_mode()
- set category = "Object"
- set name = "Toggle Pinpointer Mode"
- set src in view(1)
-
- if(usr.stat || usr.restrained() || !usr.canmove)
+ STOP_PROCESSING(SSfastprocess, src)
return
+ scan_for_target()
+ point_to_target()
+ my_god_jc_a_bomb()
+ addtimer(src, "refresh_target", 50, TRUE)
- active = 0
- icon_state = "pinoff"
- target=null
- location = null
-
- switch(alert("Please select the mode you want to put the pinpointer in.", "Pinpointer Mode Select", "Location", "Disk Recovery", "Other Signature"))
- if("Location")
- mode = 1
-
- var/locationx = input(usr, "Please input the x coordinate to search for.", "Location?" , "") as num
- if(!locationx || !(usr in view(1,src)))
- return
- var/locationy = input(usr, "Please input the y coordinate to search for.", "Location?" , "") as num
- if(!locationy || !(usr in view(1,src)))
- return
-
- var/turf/Z = get_turf(src)
-
- location = locate(locationx,locationy,Z.z)
-
- usr << "You set the pinpointer to locate [locationx],[locationy]"
-
-
- return attack_self()
-
- if("Disk Recovery")
- mode = 0
- return attack_self()
-
- if("Other Signature")
- mode = 2
- switch(alert("Search for item signature or DNA fragment?" , "Signature Mode Select" , "" , "Item" , "DNA"))
- if("Item")
- var/targetitem = input("Select item to search for.", "Item Mode Select","") as null|anything in possible_items
- if(!targetitem)
- return
- target=locate(possible_items[targetitem])
- if(!target)
- usr << "Failed to locate [targetitem]!"
- return
- usr << "You set the pinpointer to locate [targetitem]."
- if("DNA")
- var/DNAstring = input("Input DNA string to search for." , "Please Enter String." , "")
- if(!DNAstring)
- return
- for(var/mob/living/carbon/C in mob_list)
- if(!C.dna)
- continue
- if(C.dna.unique_enzymes == DNAstring)
- target = C
- break
-
- return attack_self()
-
-
-///////////////////////
-//nuke op pinpointers//
-///////////////////////
-
-
-/obj/item/weapon/pinpointer/nukeop
- var/mode = 0 //Mode 0 locates disk, mode 1 locates the shuttle
- var/obj/docking_port/mobile/home
-
-
-/obj/item/weapon/pinpointer/nukeop/attack_self(mob/user)
- if(!active)
- active = 1
- var/mode_text = "Authentication Disk Locator mode"
- if(!mode)
- workdisk()
- else
- mode_text = "Shuttle Locator mode"
- worklocation()
- user << "You activate the pinpointer([mode_text])."
- else
- active = 0
- icon_state = "pinoff"
- user << "You deactivate the pinpointer."
-
-
-/obj/item/weapon/pinpointer/nukeop/workdisk()
- if(!active) return
- if(mode) //Check in case the mode changes while operating
- worklocation()
+/obj/item/weapon/pinpointer/proc/scan_for_target() //Looks for whatever it's tracking
+ if(target)
+ if(isliving(target))
+ var/mob/living/L = target
+ if(L.stat == DEAD)
+ target = null
return
- if(bomb_set) //If the bomb is set, lead to the shuttle
- mode = 1 //Ensures worklocation() continues to work
- worklocation()
- playsound(loc, 'sound/machines/twobeep.ogg', 50, 1) //Plays a beep
- visible_message("Shuttle Locator mode actived.") //Lets the mob holding it know that the mode has changed
- return //Get outta here
- scandisk()
- if(!the_disk)
- icon_state = "pinonnull"
- return
- setDir(get_dir(src, the_disk))
- switch(get_dist(src, the_disk))
- if(0)
- icon_state = "pinondirect"
- if(1 to 8)
- icon_state = "pinonclose"
- if(9 to 16)
- icon_state = "pinonmedium"
- if(16 to INFINITY)
- icon_state = "pinonfar"
+ switch(mode)
+ if(TRACK_NUKE_DISK)
+ var/obj/item/weapon/disk/nuclear/N = locate()
+ target = N
+ if(TRACK_MALF_AI)
+ for(var/V in ai_list)
+ var/mob/living/silicon/ai/A = V
+ if(A.nuking)
+ target = A
+ for(var/V in apcs_list)
+ var/obj/machinery/power/apc/A = V
+ if(A.malfhack && A.occupier)
+ target = A
+ if(TRACK_INFILTRATOR)
+ target = SSshuttle.getShuttle("syndicate")
+ if(TRACK_OPERATIVES)
+ var/list/possible_targets = list()
+ var/turf/here = get_turf(src)
+ for(var/V in ticker.mode.syndicates)
+ var/datum/mind/M = V
+ if(M.current && M.current.stat != DEAD)
+ possible_targets |= M.current
+ var/mob/living/closest_operative = get_closest_atom(/mob/living/carbon/human, possible_targets, here)
+ if(closest_operative)
+ target = closest_operative
+ if(TRACK_ATOM)
+ if(constant_target)
+ target = constant_target
+ if(TRACK_COORDINATES)
+ var/turf/T = get_turf(src)
+ target = locate(target_x, target_y, T.z)
- spawn(5) .()
-
-
-/obj/item/weapon/pinpointer/nukeop/proc/worklocation()
+/obj/item/weapon/pinpointer/proc/point_to_target() //If we found what we're looking for, show the distance and direction
if(!active)
return
- if(!mode)
- workdisk()
+ if(!target || (mode == TRACK_ATOM && !constant_target))
+ icon_state = "pinon[nuke_warning ? "alert" : ""]null"
return
- if(!bomb_set)
- mode = 0
- workdisk()
- playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
- visible_message("Authentication Disk Locator mode actived.")
+ var/turf/here = get_turf(src)
+ var/turf/there = get_turf(target)
+ if(here.z != there.z)
+ icon_state = "pinon[nuke_warning ? "alert" : ""]null"
return
- if(!home)
- home = SSshuttle.getShuttle("syndicate")
- if(!home)
- icon_state = "pinonnull"
- return
- if(loc.z != home.z) //If you are on a different z-level from the shuttle
- icon_state = "pinonnull"
+ if(here == there)
+ icon_state = "pinon[nuke_warning ? "alert" : ""]direct"
else
- setDir(get_dir(src, home))
- switch(get_dist(src, home))
- if(0)
- icon_state = "pinondirect"
+ setDir(get_dir(here, there))
+ switch(get_dist(here, there))
if(1 to 8)
- icon_state = "pinonclose"
+ icon_state = "pinon[nuke_warning ? "alert" : "close"]"
if(9 to 16)
- icon_state = "pinonmedium"
+ icon_state = "pinon[nuke_warning ? "alert" : "medium"]"
if(16 to INFINITY)
- icon_state = "pinonfar"
+ icon_state = "pinon[nuke_warning ? "alert" : "far"]"
- spawn(5)
- .()
+/obj/item/weapon/pinpointer/proc/my_god_jc_a_bomb() //If we should get the hell back to the ship
+ for(var/obj/machinery/nuclearbomb/bomb in machines)
+ if(bomb.timing)
+ if(!nuke_warning)
+ nuke_warning = TRUE
+ playsound(src, 'sound/items/Nuke_toy_lowpower.ogg', 50, 0)
+ if(isliving(loc))
+ var/mob/living/L = loc
+ L << "Your [name] vibrates and lets out a tinny alarm. Uh oh."
-/obj/item/weapon/pinpointer/operative
- name = "operative pinpointer"
- icon = 'icons/obj/device.dmi'
- desc = "A pinpointer that leads to the first Syndicate operative detected."
- var/mob/living/carbon/nearest_op = null
+/obj/item/weapon/pinpointer/proc/switch_mode_to(new_mode) //If we shouldn't be tracking what we are
+ if(isliving(loc))
+ var/mob/living/L = loc
+ L << "Your [name] beeps as it reconfigures its tracking algorithms."
+ playsound(L, 'sound/machines/triple_beep.ogg', 50, 1)
+ mode = new_mode
+ target = null //Switch modes so we can find the new target
-/obj/item/weapon/pinpointer/operative/attack_self()
- if(!usr.mind || !(usr.mind in ticker.mode.syndicates))
- usr << "AUTHENTICATION FAILURE. ACCESS DENIED."
- return 0
- if(!active)
- active = 1
- workop()
- usr << "You activate the pinpointer."
- else
- active = 0
- icon_state = "pinoff"
- usr << "You deactivate the pinpointer."
+/obj/item/weapon/pinpointer/proc/refresh_target() //Periodically removes the target to allow the pinpointer to update (i.e. malf AI shunts, an operative dies)
+ target = null
-/obj/item/weapon/pinpointer/operative/proc/scan_for_ops()
- if(active)
- nearest_op = null //Resets nearest_op every time it scans
- var/closest_distance = 1000
- for(var/mob/living/carbon/M in mob_list)
- if(M.mind && (M.mind in ticker.mode.syndicates))
- if(get_dist(M, get_turf(src)) < closest_distance) //Actually points toward the nearest op, instead of a random one like it used to
- nearest_op = M
+/obj/item/weapon/pinpointer/syndicate //Syndicate pinpointers automatically point towards the infiltrator once the nuke is active.
+ name = "syndicate pinpointer"
+ desc = "A handheld tracking device that locks onto certain signals. It's configured to switch tracking modes once it detects the activation signal of a nuclear device."
-/obj/item/weapon/pinpointer/operative/proc/workop()
- if(active)
- scan_for_ops()
- point_at(nearest_op, 0)
- spawn(5)
- .()
- else
- return 0
-
-/obj/item/weapon/pinpointer/operative/examine(mob/user)
- ..()
- if(active)
- if(nearest_op)
- user << "Nearest operative detected is [nearest_op.real_name]."
- else
- user << "No operatives detected within scanning range."
+/obj/item/weapon/pinpointer/syndicate/cyborg //Cyborg pinpointers just look for a random operative.
+ name = "cyborg syndicate pinpointer"
+ desc = "An integrated tracking device, jury-rigged to search for living Syndicate operatives."
+ mode = TRACK_OPERATIVES
+ flags = NODROP
diff --git a/code/modules/mob/living/silicon/ai/death.dm b/code/modules/mob/living/silicon/ai/death.dm
index 7f5891f5b82e..8270aa969986 100644
--- a/code/modules/mob/living/silicon/ai/death.dm
+++ b/code/modules/mob/living/silicon/ai/death.dm
@@ -24,8 +24,9 @@
if(nuking)
set_security_level("red")
nuking = FALSE
- for(var/obj/item/weapon/pinpointer/point in pinpointer_list)
- point.the_disk = null //Point back to the disk.
+ for(var/obj/item/weapon/pinpointer/P in pinpointer_list)
+ P.switch_mode_to(TRACK_NUKE_DISK) //Party's over, back to work, everyone
+ P.nuke_warning = FALSE
if(doomsday_device)
doomsday_device.timing = FALSE
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index 26021030ff5c..3f5482fe2670 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -336,7 +336,7 @@
modules += new /obj/item/weapon/gun/projectile/revolver/grenadelauncher/cyborg(src)
modules += new /obj/item/weapon/card/emag(src)
modules += new /obj/item/weapon/crowbar/cyborg(src)
- modules += new /obj/item/weapon/pinpointer/operative(src)
+ modules += new /obj/item/weapon/pinpointer/syndicate/cyborg(src)
emag = null
fix_modules()
@@ -357,7 +357,7 @@
modules += new /obj/item/roller/robo(src)
modules += new /obj/item/weapon/card/emag(src)
modules += new /obj/item/weapon/crowbar/cyborg(src)
- modules += new /obj/item/weapon/pinpointer/operative(src)
+ modules += new /obj/item/weapon/pinpointer/syndicate/cyborg(src)
emag = null
add_module(new /obj/item/stack/medical/gauze/cyborg())
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index 9c17ba8db9bb..782692e0ac09 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -829,9 +829,6 @@
qdel(malf)
src.occupier.verbs += /mob/living/silicon/ai/proc/corereturn
src.occupier.cancel_camera()
- if ((seclevel2num(get_security_level()) == SEC_LEVEL_DELTA) && malf.nuking)
- for(var/obj/item/weapon/pinpointer/point in pinpointer_list)
- point.the_disk = src //the pinpointer will detect the shunted AI
/obj/machinery/power/apc/proc/malfvacate(forced)
@@ -843,11 +840,6 @@
src.occupier.parent.adjustOxyLoss(src.occupier.getOxyLoss())
src.occupier.parent.cancel_camera()
qdel(src.occupier)
- if (seclevel2num(get_security_level()) == SEC_LEVEL_DELTA)
- for(var/obj/item/weapon/pinpointer/point in pinpointer_list)
- for(var/mob/living/silicon/ai/A in ai_list)
- if((A.stat != DEAD) && A.nuking)
- point.the_disk = A //The pinpointer tracks the AI back into its core.
else
src.occupier << "Primary core damaged, unable to return core processes."
@@ -855,9 +847,9 @@
src.occupier.loc = src.loc
src.occupier.death()
src.occupier.gib()
- for(var/obj/item/weapon/pinpointer/point in pinpointer_list)
- point.the_disk = null //the pinpointer will go back to pointing at the nuke disc.
-
+ for(var/obj/item/weapon/pinpointer/P in pinpointer_list)
+ P.switch_mode_to(TRACK_NUKE_DISK) //Pinpointers go back to tracking the nuke disk
+ P.nuke_warning = FALSE
/obj/machinery/power/apc/surplus()
if(terminal)
diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi
index 64825e13edd4..617d7824d079 100644
Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ
diff --git a/sound/machines/triple_beep.ogg b/sound/machines/triple_beep.ogg
new file mode 100644
index 000000000000..dcd3539c811e
Binary files /dev/null and b/sound/machines/triple_beep.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index 1befd361adac..d572a21bf0cb 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -38,6 +38,7 @@
#include "code\__DEFINES\math.dm"
#include "code\__DEFINES\MC.dm"
#include "code\__DEFINES\misc.dm"
+#include "code\__DEFINES\pinpointers.dm"
#include "code\__DEFINES\pipe_construction.dm"
#include "code\__DEFINES\preferences.dm"
#include "code\__DEFINES\qdel.dm"