"
- if(contained_id)
- dat += "[contained_id] "
- dat += "Collected Points: [contained_id.points]. Reset. "
- dat += "Card goal: [contained_id.goal]. Set "
- dat += "Space Law recommends quotas of 100 points per minute they would normally serve in the brig. "
- else
- dat += "Insert Prisoner ID. "
- dat += "
Prisoner Implant Management
"
- dat += "Chemical Implants "
- var/turf/current_turf = get_turf(src)
- for(var/obj/item/implant/chem/C in GLOB.tracked_chem_implants)
- var/turf/implant_turf = get_turf(C)
- if(!is_valid_z_level(current_turf, implant_turf))
- continue//Out of range
- if(!C.imp_in)
- continue
- dat += "ID: [C.imp_in.name] | Remaining Units: [C.reagents.total_volume] "
- dat += "| Inject: "
- dat += "((1))"
- dat += "((5))"
- dat += "((10)) "
- dat += "******************************** "
- dat += "Tracking Implants "
- for(var/obj/item/implant/tracking/T in GLOB.tracked_implants)
- if(!isliving(T.imp_in))
- continue
- var/turf/implant_turf = get_turf(T)
- if(!is_valid_z_level(current_turf, implant_turf))
- continue//Out of range
-
- var/loc_display = "Unknown"
- var/mob/living/M = T.imp_in
- if(is_station_level(implant_turf.z) && !isspaceturf(M.loc))
- var/turf/mob_loc = get_turf(M)
- loc_display = mob_loc.loc
-
- dat += "ID: [T.imp_in.name] | Location: [loc_display] "
- dat += "(Message Holder) | "
- dat += "******************************** "
- dat += "{Log Out}"
- var/datum/browser/popup = new(user, "computer", "Prisoner Management Console", 400, 500)
- popup.set_content(dat)
- popup.open()
- return
-
-/obj/machinery/computer/prisoner/management/attackby(obj/item/I, mob/user, params)
- if(isidcard(I))
- if(screen)
- id_insert(user)
- else
- to_chat(user, span_danger("Unauthorized access."))
- else
- return ..()
-
-/obj/machinery/computer/prisoner/management/process()
- if(!..())
- src.updateDialog()
- return
-
-/obj/machinery/computer/prisoner/management/Topic(href, href_list)
- if(..())
+ if(.)
return
- if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr))
- usr.set_machine(src)
-
- if(href_list["id"])
- if(href_list["id"] == "insert" && !contained_id)
- id_insert(usr)
- else if(contained_id)
- switch(href_list["id"])
- if("eject")
- id_eject(usr)
- if("reset")
- contained_id.points = 0
- if("setgoal")
- var/num = tgui_input_text(usr, "Enter the prisoner's goal", "Prisoner Management", 1, 1000, 1)
- if(isnull(num))
- return
- contained_id.goal = round(num)
- else if(href_list["inject1"])
- var/obj/item/implant/I = locate(href_list["inject1"]) in GLOB.tracked_chem_implants
- if(I && istype(I))
- I.activate(1)
- else if(href_list["inject5"])
- var/obj/item/implant/I = locate(href_list["inject5"]) in GLOB.tracked_chem_implants
- if(I && istype(I))
- I.activate(5)
- else if(href_list["inject10"])
- var/obj/item/implant/I = locate(href_list["inject10"]) in GLOB.tracked_chem_implants
- if(I && istype(I))
- I.activate(10)
-
- else if(href_list["lock"])
+
+ if(!authenticated && action != "login")
+ CRASH("[usr] potentially spoofed ui action [action] on prisoner console without the console being logged in.")
+
+ if(isliving(usr))
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
+
+ switch(action)
+ if("login")
if(allowed(usr))
- screen = !screen
+ authenticated = TRUE
playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE)
else
- to_chat(usr, span_danger("Unauthorized access."))
-
- else if(href_list["warn"])
- var/warning = tgui_input_text(usr, "Enter your message here", "Messaging")
- if(!warning)
- return
- var/obj/item/implant/I = locate(href_list["warn"]) in GLOB.tracked_implants
- if(I && istype(I) && I.imp_in)
- var/mob/living/R = I.imp_in
- to_chat(R, span_hear("You hear a voice in your head saying: '[warning]'"))
- log_directed_talk(usr, R, warning, LOG_SAY, "implant message")
-
- src.add_fingerprint(usr)
- src.updateUsrDialog()
- return
+ playsound(src, 'sound/machines/terminal_error.ogg', 50, FALSE)
+ return TRUE
+
+ if("logout")
+ authenticated = FALSE
+ playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE)
+ return TRUE
+
+ if("insert_id")
+ id_insert(usr, usr.get_active_held_item())
+ return TRUE
+
+ if("eject_id")
+ id_eject(usr)
+ return TRUE
+
+ if("set_id_goal")
+ var/num = tgui_input_number(usr, "Enter the prisoner's goal", "Prisoner Management", 100, 1000, 1)
+ if(!isnum(num) || QDELETED(src) || QDELETED(contained_id) || QDELETED(usr))
+ return TRUE
+ if(!is_operational || !usr.can_perform_action(src, NEED_DEXTERITY|ALLOW_SILICON_REACH))
+ return TRUE
+
+ contained_id.goal = num
+ return TRUE
+
+ if("reset_id")
+ contained_id.points = 0
+ return TRUE
+
+ if("handle_implant")
+ var/obj/item/implant/affected_implant = locate(params["implant_ref"]) in GLOB.tracked_implants
+ if(affected_implant?.is_shown_on_console(src))
+ affected_implant.handle_management_console_action(usr, params, src)
+ return TRUE
diff --git a/code/game/machinery/computer/teleporter.dm b/code/game/machinery/computer/teleporter.dm
index dd8a051cc8e..0915d4d1d17 100644
--- a/code/game/machinery/computer/teleporter.dm
+++ b/code/game/machinery/computer/teleporter.dm
@@ -167,16 +167,16 @@
var/area/area = get_area(beacon)
targets[avoid_assoc_duplicate_keys(format_text(area.name), area_index)] = beacon
- for (var/obj/item/implant/tracking/tracking_implant in GLOB.tracked_implants)
- if (!tracking_implant.imp_in || !isliving(tracking_implant.loc) || !tracking_implant.allow_teleport)
+ for (var/obj/item/implant/beacon/tracking_beacon in GLOB.tracked_implants)
+ if (isnull(tracking_beacon.imp_in) || !isliving(tracking_beacon.loc))
continue
- var/mob/living/implanted = tracking_implant.loc
- if (implanted.stat == DEAD && implanted.timeofdeath + tracking_implant.lifespan_postmortem < world.time)
+ var/mob/living/implanted = tracking_beacon.loc
+ if (implanted.stat == DEAD && implanted.timeofdeath + tracking_beacon.lifespan_postmortem < world.time)
continue
- if (is_eligible(tracking_implant))
- targets[avoid_assoc_duplicate_keys("[implanted.real_name] ([format_text(get_area(implanted))])", area_index)] = tracking_implant
+ if (is_eligible(tracking_beacon))
+ targets[avoid_assoc_duplicate_keys("[implanted.real_name] ([format_text(get_area(implanted))])", area_index)] = tracking_beacon
else
for (var/obj/machinery/teleport/station/station as anything in power_station.linked_stations)
if (is_eligible(station) && station.teleporter_hub)
diff --git a/code/game/objects/items/implants/implant.dm b/code/game/objects/items/implants/implant.dm
index 0a0c19aee96..cc64788e597 100644
--- a/code/game/objects/items/implants/implant.dm
+++ b/code/game/objects/items/implants/implant.dm
@@ -19,6 +19,10 @@
var/allow_multiple = FALSE
///how many times this can do something, only relevant for implants with limited uses
var/uses = -1
+ ///our implant flags
+ var/implant_flags = NONE
+ ///what icon state will we represent ourselves with on the hud?
+ var/hud_icon_state = null
/obj/item/implant/proc/activate()
@@ -60,12 +64,17 @@
if(!force && !can_be_implanted_in(target))
return FALSE
- for(var/X in target.implants)
- var/obj/item/implant/other_implant = X
+ var/security_implants = 0 //Used to track how many implants with the "security" flag are in the user.
+ for(var/obj/item/implant/other_implant as anything in target.implants)
var/flags = SEND_SIGNAL(other_implant, COMSIG_IMPLANT_OTHER, args, src)
if(flags & COMPONENT_STOP_IMPLANTING)
UNSETEMPTY(target.implants)
return FALSE
+ if(!force && (other_implant.implant_flags & IMPLANT_TYPE_SECURITY))
+ security_implants++
+ if(security_implants >= SECURITY_IMPLANT_CAP) //We've found too many security implants in this mob, and will reject implantation by normal means
+ balloon_alert(user, "too many security implants!")
+ return FALSE
if(flags & COMPONENT_DELETE_NEW_IMPLANT)
UNSETEMPTY(target.implants)
qdel(src)
@@ -100,6 +109,7 @@
log_combat(user, target, "implanted", "\a [name]")
SEND_SIGNAL(src, COMSIG_IMPLANT_IMPLANTED, target, user, silent, force)
+ GLOB.tracked_implants += src
return TRUE
/**
@@ -122,12 +132,14 @@
human_source.sec_hud_set_implants()
SEND_SIGNAL(src, COMSIG_IMPLANT_REMOVED, source, silent, special)
+ GLOB.tracked_implants -= src
return TRUE
/obj/item/implant/Destroy()
if(imp_in)
removed(imp_in)
return ..()
+
/**
* Gets implant specifications for the implant pad
*/
@@ -137,3 +149,68 @@
/obj/item/implant/dropped(mob/user)
. = TRUE
..()
+
+/// Determines if the implant is visible on the implant management console.
+/// Note that this would only ever be called on implants currently inserted into a mob.
+/obj/item/implant/proc/is_shown_on_console(obj/machinery/computer/prisoner/management/console)
+ return FALSE
+
+/**
+ * Returns a list of information to show on the implant management console for this implant
+ *
+ * Unlike normal UI data, the keys of the list are shown on the UI itself, so they should be human readable.
+ */
+/obj/item/implant/proc/get_management_console_data()
+ RETURN_TYPE(/list)
+
+ var/list/info_shown = list()
+ info_shown["ID"] = imp_in.name
+ return info_shown
+
+/**
+ * Returns a list of "structs" that translate into buttons displayed on the implant management console
+ *
+ * The struct should have the following keys:
+ * * name - the name of the button, optional if button_icon is set
+ * * icon - the icon of the button, optional if button_name is set
+ * * color - the color of the button, optional
+ * * tooltip - the tooltip of the button, optional
+ * * action_key - the key that will be passed to handle_management_console_action when the button is clicked
+ * * action_params - optional, additional params passed when the button is clicked
+ */
+/obj/item/implant/proc/get_management_console_buttons()
+ SHOULD_CALL_PARENT(TRUE)
+ RETURN_TYPE(/list)
+
+ var/list/buttons = list()
+ UNTYPED_LIST_ADD(buttons, list(
+ "name" = "Self Destruct",
+ "color" = "bad",
+ "tooltip" = "Destoys the implant from within the user harmlessly.",
+ "action_key" = "self_destruct",
+ ))
+ return buttons
+
+/**
+ * Handles a button click on the implant management console
+ *
+ * * user - the mob clicking the button
+ * * params - the params passed to the button, as if this were a ui_act handler.
+ * See params["implant_action"] for the action key passed to the button
+ * (which should correspond to a button returned by get_management_console_buttons)
+ * * console - the console the button was clicked on
+ */
+/obj/item/implant/proc/handle_management_console_action(mob/user, list/params, obj/machinery/computer/prisoner/management/console)
+ SHOULD_CALL_PARENT(TRUE)
+
+ if(params["implant_action"] == "self_destruct")
+ var/warning = tgui_alert(user, "Activation will harmlessly self-destruct this implant. Proceed?", "You sure?", list("Yes", "No"))
+ if(warning != "Yes" || QDELETED(src) || QDELETED(user) || QDELETED(console) || isnull(imp_in))
+ return TRUE
+ if(!console.is_operational || !user.can_perform_action(console, NEED_DEXTERITY|ALLOW_SILICON_REACH))
+ return TRUE
+
+ to_chat(imp_in, span_hear("You feel a tiny jolt from inside of you as one of your implants fizzles out."))
+ do_sparks(number = 2, cardinal_only = FALSE, source = imp_in)
+ deconstruct()
+ return TRUE
diff --git a/code/game/objects/items/implants/implant_track.dm b/code/game/objects/items/implants/implant_track.dm
deleted file mode 100644
index 65f11c2519b..00000000000
--- a/code/game/objects/items/implants/implant_track.dm
+++ /dev/null
@@ -1,59 +0,0 @@
-/obj/item/implant/tracking
- name = "tracking implant"
- desc = "Track with this."
- actions_types = null
-
- ///for how many deciseconds after user death will the implant work?
- var/lifespan_postmortem = 6000
- ///will people implanted with this act as teleporter beacons?
- var/allow_teleport = TRUE
-
-/obj/item/implant/tracking/c38
- name = "TRAC implant"
- desc = "A smaller tracking implant that supplies power for only a few minutes."
- var/lifespan = 3000 //how many deciseconds does the implant last?
- ///The id of the timer that's qdeleting us
- var/timerid
- allow_teleport = FALSE
-
-/obj/item/implant/tracking/c38/implant(mob/living/target, mob/user, silent, force)
- . = ..()
- timerid = QDEL_IN_STOPPABLE(src, lifespan)
-
-/obj/item/implant/tracking/c38/removed(mob/living/source, silent, special)
- . = ..()
- deltimer(timerid)
- timerid = null
-
-/obj/item/implant/tracking/c38/Destroy()
- return ..()
-
-/obj/item/implant/tracking/Initialize(mapload)
- . = ..()
- GLOB.tracked_implants += src
-
-/obj/item/implant/tracking/Destroy()
- GLOB.tracked_implants -= src
- return ..()
-
-/obj/item/implanter/tracking
- imp_type = /obj/item/implant/tracking
-
-/obj/item/implanter/tracking/gps
- imp_type = /obj/item/gps/mining/internal
-
-/obj/item/implant/tracking/get_data()
- var/dat = {"Implant Specifications:
- Name: Tracking Beacon
- Life: 10 minutes after death of host.
- Important Notes: Implant [allow_teleport ? "also works" : "does not work"] as a teleporter beacon.
-
- Implant Details:
- Function: Continuously transmits low power signal. Useful for tracking.
- Special Features:
- Neuro-Safe- Specialized shell absorbs excess voltages self-destructing the chip if
- a malfunction occurs thereby securing safety of subject. The implant will melt and
- disintegrate into bio-safe elements.
- Integrity: Gradient creates slight risk of being overcharged and frying the
- circuitry. As a result neurotoxins can cause massive damage."}
- return dat
diff --git a/code/game/objects/items/implants/security/implant_beacon.dm b/code/game/objects/items/implants/security/implant_beacon.dm
new file mode 100644
index 00000000000..dea6b4ed190
--- /dev/null
+++ b/code/game/objects/items/implants/security/implant_beacon.dm
@@ -0,0 +1,43 @@
+///Essentially, just turns the implantee into a teleport beacon.
+/obj/item/implant/beacon
+ name = "beacon implant"
+ desc = "Teleports things."
+ actions_types = null
+ implant_flags = IMPLANT_TYPE_SECURITY
+ hud_icon_state = "hud_imp_beacon"
+ ///How long will the implant be teleportable to after death?
+ var/lifespan_postmortem = 10 MINUTES
+
+/obj/item/implant/beacon/get_data()
+ var/dat = {"Implant Specifications:
+ Name: Robust Corp JMP-21 Fugitive Retrieval Implant
+ Life: Deactivates upon death after ten minutes, but remains within the body.
+ Important Notes: N/A
+
+ Implant Details:
+ Function: Acts as a teleportation beacon that can be tracked by any standard bluespace transponder.
+ Using this, you can teleport directly to whoever has this implant inside of them."}
+ return dat
+
+/obj/item/implant/beacon/is_shown_on_console(obj/machinery/computer/prisoner/management/console)
+ return TRUE
+
+/obj/item/implant/beacon/get_management_console_data()
+ var/list/info_shown = ..()
+
+ var/area/destination_area = get_area(imp_in)
+ if(isnull(destination_area) || (destination_area.area_flags & NOTELEPORT))
+ info_shown["Status"] = "Implant carrier teleport signal cannot be reached!"
+ else
+ var/turf/turf_to_check = get_turf(imp_in)
+ info_shown["Status"] = "Implant carrier is in [is_safe_turf(turf_to_check, dense_atoms = TRUE) ? "a safe environment." : "a hazardous environment!"]"
+
+ return info_shown
+
+/obj/item/implanter/beacon
+ imp_type = /obj/item/implant/beacon
+
+/obj/item/implantcase/beacon
+ name = "implant case - 'Beacon'"
+ desc = "A glass case containing a beacon implant."
+ imp_type = /obj/item/implant/beacon
diff --git a/code/game/objects/items/implants/implant_chem.dm b/code/game/objects/items/implants/security/implant_chem.dm
similarity index 62%
rename from code/game/objects/items/implants/implant_chem.dm
rename to code/game/objects/items/implants/security/implant_chem.dm
index adcba5641cf..db41d8bcac2 100644
--- a/code/game/objects/items/implants/implant_chem.dm
+++ b/code/game/objects/items/implants/security/implant_chem.dm
@@ -3,6 +3,10 @@
desc = "Injects things."
icon_state = "reagents"
actions_types = null
+ implant_flags = IMPLANT_TYPE_SECURITY
+ hud_icon_state = "hud_imp_chem"
+ /// All possible injection sizes for the implant shown in the prisoner management console.
+ var/list/implant_sizes = list(1, 5, 10)
/obj/item/implant/chem/get_data()
var/dat = {"Implant Specifications:
@@ -10,24 +14,51 @@
Life: Deactivates upon death but remains within the body. Important Notes: Due to the system functioning off of nutrients in the implanted subject's body, the subject
will suffer from an increased appetite.
- Implant Details:
- Function: Contains a small capsule that can contain various chemicals. Upon receiving a specially encoded signal
+ Function: Contains a small capsule that can contain various chemicals. Upon receiving a specially encoded signal
the implant releases the chemicals directly into the blood stream.
- Special Features:Micro-Capsule- Can be loaded with any sort of chemical agent via the common syringe and can hold 50 units.
Can only be loaded while still in its original case.
- Integrity: Implant will last so long as the subject is alive."}
+ Integrity: Implant will last so long as the subject is alive, breaking down and releasing all contents on death."}
return dat
+/obj/item/implant/chem/is_shown_on_console(obj/machinery/computer/prisoner/management/console)
+ return is_valid_z_level(get_turf(console), get_turf(imp_in))
+
+/obj/item/implant/chem/get_management_console_data()
+ var/list/info_shown = ..()
+ info_shown["Volume"] = "[reagents.total_volume]u"
+ return info_shown
+
+/obj/item/implant/chem/get_management_console_buttons()
+ var/list/buttons = ..()
+ for(var/i in implant_sizes)
+ UNTYPED_LIST_ADD(buttons, list(
+ "name" = "Inject [i]u",
+ "color" = "good",
+ "action_key" = "inject",
+ "action_params" = list("amount" = i),
+ ))
+ return buttons
+
+/obj/item/implant/chem/handle_management_console_action(mob/user, list/params, obj/machinery/computer/prisoner/management/console)
+ . = ..()
+ if(.)
+ return
+
+ if(params["implant_action"] == "inject")
+ var/amount = text2num(params["amount"])
+ if(!(amount in implant_sizes))
+ return TRUE
+
+ var/reagents_inside = reagents.get_reagent_log_string()
+ activate(amount)
+ log_combat(user, imp_in, "injected [amount] units of [reagents_inside]", src)
+ return TRUE
+
/obj/item/implant/chem/Initialize(mapload)
. = ..()
create_reagents(50, OPENCONTAINER)
- GLOB.tracked_chem_implants += src
-
-/obj/item/implant/chem/Destroy()
- GLOB.tracked_chem_implants -= src
- return ..()
/obj/item/implant/chem/implant(mob/living/target, mob/user, silent = FALSE, force = FALSE)
. = ..()
diff --git a/code/game/objects/items/implants/implant_exile.dm b/code/game/objects/items/implants/security/implant_exile.dm
similarity index 93%
rename from code/game/objects/items/implants/implant_exile.dm
rename to code/game/objects/items/implants/security/implant_exile.dm
index 056ccd0ff9a..5c33d146b3a 100644
--- a/code/game/objects/items/implants/implant_exile.dm
+++ b/code/game/objects/items/implants/security/implant_exile.dm
@@ -5,11 +5,14 @@
name = "exile implant"
desc = "Prevents you from returning from away missions."
actions_types = null
+ implant_flags = IMPLANT_TYPE_SECURITY
+ hud_icon_state = "hud_imp_exile"
/obj/item/implant/exile/get_data()
var/dat = {"Implant Specifications: Name: Nanotrasen Employee Exile Implant
- Implant Details: The onboard gateway system has been modified to reject entry by individuals containing this implant. "}
+ Implant Details: The onboard gateway system has been modified to reject entry by individuals containing this implant.
+ Additionally, station mining shuttles will lock their controls if handled by someone with this implant. "}
return dat
diff --git a/code/game/objects/items/implants/security/implant_noteleport.dm b/code/game/objects/items/implants/security/implant_noteleport.dm
new file mode 100644
index 00000000000..be4ec29659a
--- /dev/null
+++ b/code/game/objects/items/implants/security/implant_noteleport.dm
@@ -0,0 +1,45 @@
+///Blocks the implantee from being teleported
+/obj/item/implant/teleport_blocker
+ name = "bluespace grounding implant"
+ desc = "Grounds your bluespace signature in baseline reality, whatever the hell that means."
+ actions_types = null
+ implant_flags = IMPLANT_TYPE_SECURITY
+ hud_icon_state = "hud_imp_noteleport"
+
+/obj/item/implant/teleport_blocker/get_data()
+ var/dat = {"Implant Specifications:
+ Name: Robust Corp EXP-001 'Bluespace Grounder'
+ Implant Details: Upon implantation, grounds the user's bluespace signature to their currently occupied plane of existence.
+ Most, if not all forms of teleportation on the implantee will be rendered ineffective. Useful for keeping especially slippery prisoners in place. "}
+ return dat
+
+/obj/item/implant/teleport_blocker/implant(mob/living/target, mob/user, silent = FALSE, force = FALSE)
+ . = ..()
+ if(!. || !isliving(target))
+ return FALSE
+ RegisterSignal(target, COMSIG_MOVABLE_TELEPORTING, PROC_REF(on_teleport))
+ return TRUE
+
+/obj/item/implant/teleport_blocker/removed(mob/target, silent = FALSE, special = FALSE)
+ . = ..()
+ if(!. || !isliving(target))
+ return FALSE
+ UnregisterSignal(target, COMSIG_MOVABLE_TELEPORTING)
+ return TRUE
+
+/// Signal for COMSIG_MOVABLE_TELEPORTED that blocks teleports and stuns the would-be-teleportee.
+/obj/item/implant/teleport_blocker/proc/on_teleport(mob/living/teleportee, atom/destination, channel)
+ SIGNAL_HANDLER
+
+ to_chat(teleportee, span_holoparasite("You feel yourself teleporting, but are suddenly flung back to where you just were!"))
+
+ teleportee.apply_status_effect(/datum/status_effect/incapacitating/paralyzed, 5 SECONDS)
+ var/datum/effect_system/spark_spread/quantum/spark_system = new()
+ spark_system.set_up(5, TRUE, teleportee)
+ spark_system.start()
+ return COMPONENT_BLOCK_TELEPORT
+
+/obj/item/implantcase/teleport_blocker
+ name = "implant case - 'Bluespace Grounding'"
+ desc = "A glass case containing a bluespace grounding implant."
+ imp_type = /obj/item/implant/teleport_blocker
diff --git a/code/game/objects/items/implants/security/implant_track.dm b/code/game/objects/items/implants/security/implant_track.dm
new file mode 100644
index 00000000000..652d9d6507a
--- /dev/null
+++ b/code/game/objects/items/implants/security/implant_track.dm
@@ -0,0 +1,87 @@
+/obj/item/implant/tracking
+ name = "tracking implant"
+ desc = "Track with this."
+ actions_types = null
+ implant_flags = IMPLANT_TYPE_SECURITY
+ hud_icon_state = "hud_imp_tracking"
+
+ ///How long will the implant continue to function after death?
+ var/lifespan_postmortem = 10 MINUTES
+
+/obj/item/implant/tracking/get_data()
+ var/dat = {"Implant Specifications:
+ Name: Robust Corp EYE-5 Convict Parole Implant
+ Life: 10 minutes after death of host.
+
+ Implant Details:
+ Function: Continuously transmits low power signal. Can be tracked from a prisoner management console.
+ Special Features:
+ Neuro-Safe- Specialized shell absorbs excess voltages self-destructing the chip if
+ a malfunction occurs thereby securing safety of subject. The implant will melt and
+ disintegrate into bio-safe elements. "}
+ return dat
+
+/obj/item/implant/tracking/is_shown_on_console(obj/machinery/computer/prisoner/management/console)
+ if(imp_in.stat == DEAD && imp_in.timeofdeath + lifespan_postmortem < world.time)
+ return FALSE
+ if(!is_valid_z_level(get_turf(console), get_turf(imp_in)))
+ return FALSE
+ return TRUE
+
+/obj/item/implant/tracking/get_management_console_data()
+ var/list/info_shown = ..()
+ info_shown["Location"] = get_area_name(imp_in, format_text = TRUE) || "Unknown"
+ return info_shown
+
+/obj/item/implant/tracking/get_management_console_buttons()
+ var/list/buttons = ..()
+ UNTYPED_LIST_ADD(buttons, list(
+ "name" = "Warn",
+ "color" = "average",
+ "tooltip" = "Sends a message directly to the target's brain.",
+ "action_key" = "warn",
+ ))
+ return buttons
+
+/obj/item/implant/tracking/handle_management_console_action(mob/user, list/params, obj/machinery/computer/prisoner/management/console)
+ . = ..()
+ if(.)
+ return
+
+ if(params["implant_action"] == "warn")
+ var/warning = tgui_input_text(user, "What warning do you want to send to [imp_in.name]?", "Messaging")
+ if(!warning || QDELETED(src) || QDELETED(user) || QDELETED(console) || isnull(imp_in))
+ return TRUE
+ if(!console.is_operational || !user.can_perform_action(console, NEED_DEXTERITY|ALLOW_SILICON_REACH))
+ return TRUE
+
+ to_chat(imp_in, span_hear("You hear a voice in your head saying: '[warning]'"))
+ log_directed_talk(user, imp_in, warning, LOG_SAY, "implant message")
+ return TRUE
+
+/obj/item/implant/tracking/c38
+ name = "TRAC implant"
+ desc = "A smaller tracking implant that supplies power for only a few minutes."
+ implant_flags = NONE
+ ///How long before this implant self-deletes?
+ var/lifespan = 5 MINUTES
+ ///The id of the timer that's qdeleting us
+ var/timerid
+
+/obj/item/implant/tracking/c38/implant(mob/living/target, mob/user, silent, force)
+ . = ..()
+ timerid = QDEL_IN_STOPPABLE(src, lifespan)
+
+/obj/item/implant/tracking/c38/removed(mob/living/source, silent, special)
+ . = ..()
+ deltimer(timerid)
+ timerid = null
+
+/obj/item/implant/tracking/c38/Destroy()
+ return ..()
+
+/obj/item/implanter/tracking
+ imp_type = /obj/item/implant/tracking
+
+/obj/item/implanter/tracking/gps
+ imp_type = /obj/item/gps/mining/internal
diff --git a/code/game/objects/items/storage/boxes/implant_boxes.dm b/code/game/objects/items/storage/boxes/implant_boxes.dm
new file mode 100644
index 00000000000..1da12ba3285
--- /dev/null
+++ b/code/game/objects/items/storage/boxes/implant_boxes.dm
@@ -0,0 +1,82 @@
+/obj/item/storage/box/trackimp
+ name = "boxed tracking implant kit"
+ desc = "Box full of scum-bag tracking utensils."
+ icon_state = "secbox"
+ illustration = "implant"
+
+/obj/item/storage/box/trackimp/PopulateContents()
+ var/static/items_inside = list(
+ /obj/item/implantcase/tracking = 4,
+ /obj/item/implanter = 1,
+ /obj/item/implantpad = 1,
+ /obj/item/locator = 1,
+ )
+ generate_items_inside(items_inside,src)
+
+/obj/item/storage/box/minertracker
+ name = "boxed tracking implant kit"
+ desc = "For finding those who have died on the accursed lavaworld."
+ illustration = "implant"
+
+/obj/item/storage/box/minertracker/PopulateContents()
+ var/static/items_inside = list(
+ /obj/item/implantcase/tracking = 2,
+ /obj/item/implantcase/beacon = 2,
+ /obj/item/implanter = 1,
+ /obj/item/implantpad = 1,
+ /obj/item/locator = 1,
+ )
+ generate_items_inside(items_inside,src)
+
+/obj/item/storage/box/chemimp
+ name = "boxed chemical implant kit"
+ desc = "Box of stuff used to implant chemicals."
+ illustration = "implant"
+
+/obj/item/storage/box/chemimp/PopulateContents()
+ var/static/items_inside = list(
+ /obj/item/implantcase/chem = 5,
+ /obj/item/implanter = 1,
+ /obj/item/implantpad = 1,
+ )
+ generate_items_inside(items_inside,src)
+
+/obj/item/storage/box/exileimp
+ name = "boxed exile implant kit"
+ desc = "Box of exile implants. It has a picture of a clown being booted through the Gateway."
+ illustration = "implant"
+
+/obj/item/storage/box/exileimp/PopulateContents()
+ var/static/items_inside = list(
+ /obj/item/implantcase/exile = 5,
+ /obj/item/implanter = 1,
+ /obj/item/implantpad = 1,
+ )
+ generate_items_inside(items_inside,src)
+
+/obj/item/storage/box/beaconimp
+ name = "boxed beacon implant kit"
+ desc = "Contains a set of beacon implants. There's a warning label on the side warning to always check the safety of your teleport destination, \
+ accompanied by a cheery drawing of a security officer saying 'look before you leap!'"
+ illustration = "implant"
+
+/obj/item/storage/box/beaconimp/PopulateContents()
+ var/static/items_inside = list(
+ /obj/item/implantcase/beacon = 3,
+ /obj/item/implanter = 1,
+ /obj/item/implantpad = 1,
+ )
+ generate_items_inside(items_inside,src)
+
+/obj/item/storage/box/teleport_blocker
+ name = "boxed bluespace grounding implant kit"
+ desc = "Box of bluespace grounding implants. There's a drawing on the side -- A figure wearing some intimidating robes, frowning and shedding a single tear."
+ illustration = "implant"
+
+/obj/item/storage/box/teleport_blocker/PopulateContents()
+ var/static/items_inside = list(
+ /obj/item/implantcase/teleport_blocker = 2,
+ /obj/item/implanter = 1,
+ /obj/item/implantpad = 1,
+ )
+ generate_items_inside(items_inside,src)
diff --git a/code/game/objects/items/storage/boxes/security_boxes.dm b/code/game/objects/items/storage/boxes/security_boxes.dm
index 9c401f99907..8e55986fb40 100644
--- a/code/game/objects/items/storage/boxes/security_boxes.dm
+++ b/code/game/objects/items/storage/boxes/security_boxes.dm
@@ -69,60 +69,6 @@
for(var/i in 1 to 5)
new /obj/item/grenade/empgrenade(src)
-/obj/item/storage/box/trackimp
- name = "boxed tracking implant kit"
- desc = "Box full of scum-bag tracking utensils."
- icon_state = "secbox"
- illustration = "implant"
-
-/obj/item/storage/box/trackimp/PopulateContents()
- var/static/items_inside = list(
- /obj/item/implantcase/tracking = 4,
- /obj/item/implanter = 1,
- /obj/item/implantpad = 1,
- /obj/item/locator = 1,
- )
- generate_items_inside(items_inside,src)
-
-/obj/item/storage/box/minertracker
- name = "boxed tracking implant kit"
- desc = "For finding those who have died on the accursed lavaworld."
- illustration = "implant"
-
-/obj/item/storage/box/minertracker/PopulateContents()
- var/static/items_inside = list(
- /obj/item/implantcase/tracking = 3,
- /obj/item/implanter = 1,
- /obj/item/implantpad = 1,
- /obj/item/locator = 1,
- )
- generate_items_inside(items_inside,src)
-
-/obj/item/storage/box/chemimp
- name = "boxed chemical implant kit"
- desc = "Box of stuff used to implant chemicals."
- illustration = "implant"
-
-/obj/item/storage/box/chemimp/PopulateContents()
- var/static/items_inside = list(
- /obj/item/implantcase/chem = 5,
- /obj/item/implanter = 1,
- /obj/item/implantpad = 1,
- )
- generate_items_inside(items_inside,src)
-
-/obj/item/storage/box/exileimp
- name = "boxed exile implant kit"
- desc = "Box of exile implants. It has a picture of a clown being booted through the Gateway."
- illustration = "implant"
-
-/obj/item/storage/box/exileimp/PopulateContents()
- var/static/items_inside = list(
- /obj/item/implantcase/exile = 5,
- /obj/item/implanter = 1,
- )
- generate_items_inside(items_inside,src)
-
/obj/item/storage/box/prisoner
name = "box of prisoner IDs"
desc = "Take away their last shred of dignity, their name."
diff --git a/code/game/objects/items/teleportation.dm b/code/game/objects/items/teleportation.dm
index 1711110dd7b..5317afe4a78 100644
--- a/code/game/objects/items/teleportation.dm
+++ b/code/game/objects/items/teleportation.dm
@@ -24,7 +24,7 @@
throw_speed = 3
throw_range = 7
custom_materials = list(/datum/material/iron= SMALL_MATERIAL_AMOUNT * 4)
- var/tracking_range = 20
+ var/tracking_range = 35
/obj/item/locator/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
@@ -72,22 +72,22 @@
var/list/track_implants = list()
- for (var/obj/item/implant/tracking/W in GLOB.tracked_implants)
- if (!W.imp_in || !isliving(W.loc))
+ for (var/obj/item/implant/beacon/tracking_beacon in GLOB.tracked_implants)
+ if (!tracking_beacon.imp_in || !isliving(tracking_beacon.loc))
continue
else
- var/mob/living/M = W.loc
- if (M.stat == DEAD)
- if (M.timeofdeath + W.lifespan_postmortem < world.time)
+ var/mob/living/living_mob = tracking_beacon.loc
+ if (living_mob.stat == DEAD)
+ if (living_mob.timeofdeath + tracking_beacon.lifespan_postmortem < world.time)
continue
- var/turf/tr = get_turf(W)
+ var/turf/tr = get_turf(tracking_beacon)
var/distance = max(abs(tr.x - sr.x), abs(tr.y - sr.y))
if(distance > tracking_range)
continue
var/D = dir2text(get_dir(sr, tr))
- track_implants += list(list(name = W.imp_in.name, direction = D, distance = distance))
+ track_implants += list(list(name = tracking_beacon.imp_in.name, direction = D, distance = distance))
data["trackimplants"] = track_implants
return data
diff --git a/code/modules/cargo/packs/security.dm b/code/modules/cargo/packs/security.dm
index 42a6c03544a..04129eb08a5 100644
--- a/code/modules/cargo/packs/security.dm
+++ b/code/modules/cargo/packs/security.dm
@@ -340,3 +340,17 @@
access_view = ACCESS_SECURITY
contains = list(/obj/item/clothing/glasses/sunglasses = 1)
crate_name = "sunglasses crate"
+
+/datum/supply_pack/security/armory/beacon_imp
+ name = "Beacon Implants Crate"
+ desc = "Contains five Beacon implants."
+ cost = CARGO_CRATE_VALUE * 5.5
+ contains = list(/obj/item/storage/box/beaconimp)
+ crate_name = "beacon implant crate"
+
+/datum/supply_pack/security/armory/teleport_blocker_imp
+ name = "Bluespace Grounding Implants Crate"
+ desc = "Contains five Bluespace Grounding implants."
+ cost = CARGO_CRATE_VALUE * 7
+ contains = list(/obj/item/storage/box/teleport_blocker)
+ crate_name = "bluespace grounding implant crate"
diff --git a/code/modules/mining/mine_items.dm b/code/modules/mining/mine_items.dm
index d265ea0f463..f1a661e6a48 100644
--- a/code/modules/mining/mine_items.dm
+++ b/code/modules/mining/mine_items.dm
@@ -103,6 +103,13 @@
if (HAS_TRAIT(user, TRAIT_FORBID_MINING_SHUTTLE_CONSOLE_OUTSIDE_STATION) && !is_station_level(user.z))
to_chat(user, span_warning("You get the feeling you shouldn't mess with this."))
return
+
+ if(isliving(user))
+ var/mob/living/living_user = user
+ for(var/obj/item/implant/exile/exile_implant in living_user.implants)
+ to_chat(living_user, span_warning("A warning flashes across the screen, and the shuttle controls lock in response to your exile implant."))
+ return
+
return ..()
/obj/machinery/computer/shuttle/mining/common
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index fcdc37fdbaa..c0a1836dcfb 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -5,7 +5,7 @@
icon = 'icons/mob/human/human.dmi'
icon_state = "human_basic"
appearance_flags = KEEP_TOGETHER|TILE_BOUND|PIXEL_SCALE|LONG_GLIDE
- hud_possible = list(HEALTH_HUD,STATUS_HUD,ID_HUD,WANTED_HUD,IMPLOYAL_HUD,IMPCHEM_HUD,IMPTRACK_HUD,ANTAG_HUD,GLAND_HUD,SENTIENT_DISEASE_HUD,FAN_HUD,PERMIT_HUD, DNR_HUD) //SKYRAT EDIT ADDITION - PERMIT_HUD, DNR_HUD
+ hud_possible = list(HEALTH_HUD,STATUS_HUD,ID_HUD,WANTED_HUD,IMPLOYAL_HUD,IMPSEC_FIRST_HUD,IMPSEC_SECOND_HUD,ANTAG_HUD,GLAND_HUD,SENTIENT_DISEASE_HUD,FAN_HUD,PERMIT_HUD, DNR_HUD) //SKYRAT EDIT ADDITION - PERMIT_HUD, DNR_HUD
hud_type = /datum/hud/human
pressure_resistance = 25
can_buckle = TRUE
diff --git a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
index e7870537805..b604c5f8eac 100644
--- a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
+++ b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
@@ -42,7 +42,7 @@
/obj/item/ammo_box/c38/trac
name = "speed loader (.38 TRAC)"
- desc = "Designed to quickly reload revolvers. TRAC bullets embed a tracking implant within the target's body. The implant's signal is incompatible with teleporters."
+ desc = "Designed to quickly reload revolvers. TRAC bullets embed a tracking implant within the target's body."
ammo_type = /obj/item/ammo_casing/c38/trac
ammo_band_color = "#7b6383"
diff --git a/icons/mob/huds/hud.dmi b/icons/mob/huds/hud.dmi
index d71ba4b0940..18527378473 100644
Binary files a/icons/mob/huds/hud.dmi and b/icons/mob/huds/hud.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index b5f3fc0f289..0e2aaa172c1 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -109,6 +109,7 @@
#include "code\__DEFINES\hud.dm"
#include "code\__DEFINES\icon_smoothing.dm"
#include "code\__DEFINES\id_cards.dm"
+#include "code\__DEFINES\implants.dm"
#include "code\__DEFINES\important_recursive_contents.dm"
#include "code\__DEFINES\injection.dm"
#include "code\__DEFINES\input.dm"
@@ -2506,10 +2507,8 @@
#include "code\game\objects\items\grenades\syndieminibomb.dm"
#include "code\game\objects\items\implants\implant.dm"
#include "code\game\objects\items\implants\implant_abductor.dm"
-#include "code\game\objects\items\implants\implant_chem.dm"
#include "code\game\objects\items\implants\implant_clown.dm"
#include "code\game\objects\items\implants\implant_deathrattle.dm"
-#include "code\game\objects\items\implants\implant_exile.dm"
#include "code\game\objects\items\implants\implant_explosive.dm"
#include "code\game\objects\items\implants\implant_freedom.dm"
#include "code\game\objects\items\implants\implant_krav_maga.dm"
@@ -2518,12 +2517,16 @@
#include "code\game\objects\items\implants\implant_spell.dm"
#include "code\game\objects\items\implants\implant_stealth.dm"
#include "code\game\objects\items\implants\implant_storage.dm"
-#include "code\game\objects\items\implants\implant_track.dm"
#include "code\game\objects\items\implants\implantcase.dm"
#include "code\game\objects\items\implants\implantchair.dm"
#include "code\game\objects\items\implants\implanter.dm"
#include "code\game\objects\items\implants\implantpad.dm"
#include "code\game\objects\items\implants\implantuplink.dm"
+#include "code\game\objects\items\implants\security\implant_beacon.dm"
+#include "code\game\objects\items\implants\security\implant_chem.dm"
+#include "code\game\objects\items\implants\security\implant_exile.dm"
+#include "code\game\objects\items\implants\security\implant_noteleport.dm"
+#include "code\game\objects\items\implants\security\implant_track.dm"
#include "code\game\objects\items\kirby_plants\kirbyplants.dm"
#include "code\game\objects\items\kirby_plants\organic_plants.dm"
#include "code\game\objects\items\kirby_plants\synthetic_plants.dm"
@@ -2596,6 +2599,7 @@
#include "code\game\objects\items\storage\boxes\clothes_boxes.dm"
#include "code\game\objects\items\storage\boxes\engineering_boxes.dm"
#include "code\game\objects\items\storage\boxes\food_boxes.dm"
+#include "code\game\objects\items\storage\boxes\implant_boxes.dm"
#include "code\game\objects\items\storage\boxes\job_boxes.dm"
#include "code\game\objects\items\storage\boxes\medical_boxes.dm"
#include "code\game\objects\items\storage\boxes\science_boxes.dm"
diff --git a/tgui/packages/tgui/interfaces/PrisonerManagement.tsx b/tgui/packages/tgui/interfaces/PrisonerManagement.tsx
new file mode 100644
index 00000000000..73c7ceadc93
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PrisonerManagement.tsx
@@ -0,0 +1,272 @@
+import { useBackend, useSharedState } from '../backend';
+import { BooleanLike } from 'common/react';
+import {
+ Box,
+ Button,
+ Icon,
+ LabeledList,
+ NoticeBox,
+ Section,
+ Stack,
+ Tabs,
+} from '../components';
+import { Window } from '../layouts';
+
+type byondRef = string;
+
+type IDInfo = {
+ name: string;
+ points: number;
+ goal: number;
+};
+
+type DMButton = {
+ name?: string;
+ icon?: string;
+ color?: string;
+ tooltip?: string;
+ action_key: string;
+ action_params: Record;
+};
+
+type ImplantInfo = {
+ info: Record;
+ buttons: DMButton[];
+ category: string;
+ ref: byondRef;
+};
+
+type Data = {
+ authorized: BooleanLike;
+ inserted_id: IDInfo | null;
+ implants: ImplantInfo[];
+};
+
+const ImplantDisplay = (props: { implant: ImplantInfo }) => {
+ const { act } = useBackend();
+ const { info, buttons, ref } = props.implant;
+
+ return (
+
+
+
+ {Object.entries(info).map(([key, value]) => (
+
+ {value}
+
+ ))}
+ {buttons.length !== 0 && (
+
+ {buttons.map((button) => (
+
+ ))}
+
+ )}
+
+
+
+
+ );
+};
+
+// When given a list of implants, sorts them by category
+const sortImplants = (implants: ImplantInfo[]) => {
+ const implantsByCategory: Record = implants.reduce(
+ (acc, implant) => {
+ if (implant.category in acc) {
+ acc[implant.category].push(implant);
+ } else {
+ acc[implant.category] = [implant];
+ }
+ return acc;
+ },
+ {},
+ );
+
+ return implantsByCategory;
+};
+
+// Converts a category ("tracking implant") to a more readable format ("Tracking")
+// Does this by capitalizing the first letter of the first word and removing the rest
+const formatCategory = (category: string) => {
+ const firstWord = category.split(' ')[0];
+ return firstWord.charAt(0).toUpperCase() + firstWord.slice(1);
+};
+
+const AllImplantDisplay = (props: { implants: ImplantInfo[] }) => {
+ const implantsByCategory: Record = sortImplants(
+ props.implants,
+ );
+
+ const [implantTab, setImplantTab] = useSharedState(
+ 'implantTab',
+ Object.keys(implantsByCategory)[0],
+ );
+
+ return (
+
+
+
+ {Object.entries(implantsByCategory).map(([category, implants]) => (
+ setImplantTab(category)}
+ >
+ {formatCategory(category)}
+
+ ))}
+
+
+
+ {implantTab && implantsByCategory && implantsByCategory[implantTab] ? (
+ implantsByCategory[implantTab].map((implant) => (
+
+ ))
+ ) : (
+ No implants detected.
+ )}
+
+
+ );
+};
+
+const IdShowcase = (props: { id: IDInfo | null }) => {
+ const { act } = useBackend();
+ const { id } = props;
+
+ return (
+
+
+
+ {id ? (
+ <>
+
+
+
+
+
+
+ >
+ ) : (
+
+
+
+ )}
+
+
+ {!!id && (
+
+
+ Space Law recommends quotas of 100 points per minute they would
+ normally serve in the brig.
+
+
+ )}
+
+ );
+};
+
+const ManagementConsole = () => {
+ const { act, data } = useBackend();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Secure Your Workspace.
+
+
+
+
+ );
+};
+
+// I copied this from security records,
+// should probably make this a generic component
+const LogIn = () => {
+ const { act } = useBackend();
+
+ return (
+
+
+
+
+
+
+
+
+ Nanotrasen SecurityHUB
+
+
+
+
+ You are not logged in.
+
+
+
+
+
+ );
+};
+
+export const PrisonerManagement = () => {
+ const { data } = useBackend();
+ const { authorized } = data;
+ return (
+
+
+ {authorized ? : }
+
+
+ );
+};