"Security Implant" rework, prisoner management console updates (#79882)

## About The Pull Request

For the vernacular purposes of the following PR body -- "Security
Implant" refers to the existing subset of implants given, by security,
to captured prisoners and such as a punitive, controlling measure. This
includes the chemical, tracking, and maybe exile implants.

This revamps the functionality of how "security" implants are displayed
on huds, prisoner management console implant controls/readouts, and
their instrumentality. It was also, ultimately, an attempt at nerfing
the tracking implant that spiralled far out of control.

Rather than only displaying chemical on the right and tracking on the
left, all implants with the "security implant" flag will be trackable on
SecHuds. A maximum of two can be implanted at once. This is both due to
technical limitations, but also conveniently provides security a limit
to consider when choosing implants.

Implants now also occupy their HUD slot based on the order they were
implanted in, rather than always occupying the same spot. Neat!


![image](https://github.com/tgstation/tgstation/assets/28870487/68b17dbb-cda4-4c3b-96d4-b3bbcf49b80e)

From two (three if you count the exile implant), there are now five
security implants. _The tracker implant has been split into two of these
implants._

<details>
<summary>Summary of the implants, functions, changes:</summary>
<br>

- **Tracker (Red)** -- No longer grants teleporter beacon. Tracking
radius has been increased from 20 to 35 tiles. The Prisoner Management
Console will now list the area the prisoner is occupying as well.
Disables after the implantee is dead for 10 minutes.
- **Chemical (Blue)** -- No mechanical changes. The implant pad readout
has been modified slightly.
- **Exile (Green)** -- In addition to past functionality, station
shuttle controls (public, mining, etc.) will be unresponsive for the
implantee. Flimsy, but more effective than a stern warning not to come
back from lavaland.
- **Beacon (Yellow)** -- Implantee becomes a teleporter beacon. The
prisoner console will report if their currently occupied area is
hazardous or not, so half of the security team doesn't blindly teleport
into space or lava. Disables after the implantee is dead for 10 minutes.
Available from Cargo.
- **Teleport Blocker (Deep Blue, not shown)** -- Prevents the implantee
from being teleported. Ever wanted to keep a wizard or cultist in a
cell? This is where you can start. Available from Cargo, expensive and
scarce.

Each of the implants has some application that would benefit security if
used on a captured criminal. Their usefulness may overlap in some
places, but the overall range of control these implants give security is
broadened.

</details>

The implant control console has also been given a small facelift.
Certain implants provide more useful readouts that can help officers
locate, control, or capture an implantee, rewarding cooperation between
officers.

It has also been totally converted into TGUI by @MrMelbert. Kickass!

Also, You can now remotely destroy implants, either to relieve criminals
from their punishment or to make room for a different implant. Wardens
should keep hold of their ID and remember to log out, since a motivated
convict could use it to shed their implants!


![tgui](https://github.com/tgstation/tgstation/assets/28870487/3c2ae99f-9c1d-4b18-b4cb-942cc96bcafe)

Everything made in this PR _should_ be scaleable enough to allow for new
security implant types to be implemented with relative ease. The
teleport-blocker implant was a last minute attempt to prove it to
myself. I had a few more ideas for implants in my head, but figured this
PR was already getting big and ugly enough. That is all for another day.

I truly apologize if there's anything I've missed in here. I did a lot
of this over a long period of time and kind of just... sat on it for a
while. If there's any confusing our unexplained changes, feel free to
point them out and I'll try to give an explanation.
## Why It's Good For The Game

The goal of this PR is to give a bit more depth to security's armory
implants. The intent is to present a choice in what implants are given
(rather than just tracker and maybe chem if you're feeling spiteful),
and to make them more useful as punitive/monitoring tools.

The tracker implant needed a nerf (and probably still does regardless of
this PR's success). It's never used for tracking since the teleporter
beacon is much more direct (+ gives a virtually free attack
opportunity), and the tracking range was incredibly subpar. I'd rather
not take toys away from security, but having the best option not be
roundstart gear feels like a fair compromise.

Warden content. Wardens have more gear to budget for and use at their
own (or the HOSes) discretion. The changes to the prisoner console allow
them to coordinate with officers to get good value out of the implants
they've chosen for an implantee.

Gives antagonists an alternate way to get de-implanted, without external
help, that can only be granted at the fault of security. Wardens who
dish out implants must keep an eye on the people carrying them!
## Changelog
🆑 Rhials, MrMelbert
add: The Tracker implant has had its teleport beacon functionality
migrated to the new (cargo accessible) Beacon implant.
add: Teleport Blocker security implant, that prevents the implantee from
teleporting by any means. Purchasable from cargo.
add: Security implants may now be harmlessly self-destructed at the
Prisoner Management Console.
balance: The Tracker implant tracking radius has increased from 20 to 35
tiles. The Prisoner Management Console will track and display the area
the implantee is in as well.
balance: The exile implant now prevents implantees from operating
shuttle controls.
code: Various code improvements and removal of unused vars in the
Prisoner Management Console
code: The HUD slots for chem/tracking implants have been converted to
display any implant with the IMPLANT_TYPE_SECURITY flag and an
associated sprite.
spellcheck: Modifies various implant pad readouts, removing false
information and rewriting some sections.
/🆑

---------

Co-authored-by: MrMelbert <kmelbert4@gmail.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
This commit is contained in:
Rhials
2023-12-09 17:15:19 +01:00
committed by GitHub
co-authored by MrMelbert MrMelbert
parent 71a1fee2f1
commit 16bdcf409c
24 changed files with 838 additions and 314 deletions
@@ -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 = {"<b>Implant Specifications:</b><BR>
<b>Name:</b> Robust Corp JMP-21 Fugitive Retrieval Implant<BR>
<b>Life:</b> Deactivates upon death after ten minutes, but remains within the body.<BR>
<b>Important Notes: N/A</B><BR>
<HR>
<b>Implant Details: </b><BR>
<b>Function:</b> 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
@@ -0,0 +1,104 @@
/obj/item/implant/chem
name = "chem implant"
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 = {"<b>Implant Specifications:</b><BR>
<b>Name:</b> Robust Corp MJ-420 Prisoner Management Implant<BR>
<b>Life:</b> Deactivates upon death but remains within the body.<BR>
<b>Important Notes: Due to the system functioning off of nutrients in the implanted subject's body, the subject<BR>
will suffer from an increased appetite.</B><BR>
<b>Implant Details:</b><BR>
<i>Function:</i> Contains a small capsule that can contain various chemicals. Upon receiving a specially encoded signal<BR>
the implant releases the chemicals directly into the blood stream.<BR>
<i>Micro-Capsule</i>- Can be loaded with any sort of chemical agent via the common syringe and can hold 50 units.<BR>
Can only be loaded while still in its original case.<BR>
<b>Integrity:</b> 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)
/obj/item/implant/chem/implant(mob/living/target, mob/user, silent = FALSE, force = FALSE)
. = ..()
if(.)
RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_death))
/obj/item/implant/chem/removed(mob/target, silent = FALSE, special = FALSE)
. = ..()
if(.)
UnregisterSignal(target, COMSIG_LIVING_DEATH)
/obj/item/implant/chem/proc/on_death(mob/living/source)
SIGNAL_HANDLER
INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/item/implant/chem, activate), reagents.total_volume)
/obj/item/implant/chem/activate(cause)
. = ..()
if(!cause || !imp_in)
return
var/mob/living/carbon/R = imp_in
var/injectamount = null
if (cause == "action_button")
injectamount = reagents.total_volume
else
injectamount = cause
reagents.trans_to(R, injectamount)
to_chat(R, span_hear("You hear a faint beep."))
if(!reagents.total_volume)
to_chat(R, span_hear("You hear a faint click from your chest."))
qdel(src)
/obj/item/implantcase/chem
name = "implant case - 'Remote Chemical'"
desc = "A glass case containing a remote chemical implant."
imp_type = /obj/item/implant/chem
/obj/item/implantcase/chem/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/reagent_containers/syringe) && imp)
W.afterattack(imp, user, TRUE, params)
return TRUE
else
return ..()
@@ -0,0 +1,62 @@
//Exile implants will allow you to use the station gate, but not return home.
//This will allow security to exile badguys/for badguys to exile their kill targets
/obj/item/implant/exile
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 = {"<b>Implant Specifications:</b><BR>
<b>Name:</b> Nanotrasen Employee Exile Implant<BR>
<b>Implant Details:</b> 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.<BR>"}
return dat
///Used to help the staff of the space hotel resist the urge to use the space hotel's incredibly alluring roundstart teleporter to ignore their flavor/greeting text and come to the station.
/obj/item/implant/exile/noteleport
name = "anti-teleportation implant"
desc = "Uses impressive bluespace grounding techniques to deny the person implanted by this implant the ability to teleport (or be teleported). Used by certain slavers (or particularly strict employers) to keep their slaves from using teleporters to escape their grasp."
/obj/item/implant/exile/noteleport/get_data()
var/dat = {"<b>Implant Specifications:</b><BR>
<b>Name:</b> Anti-Teleportation Implant<BR>
<b>Implant Details:</b> Keeps the implantee from using most teleportation devices. In addition, it spoofs the implant signature of an exile implant to keep the implantee from using certain gateway systems.<BR>"}
return dat
/obj/item/implant/exile/noteleport/implant(mob/living/target, mob/user, silent = FALSE, force = FALSE)
. = ..()
if(!. || !isliving(target))
return FALSE
var/mob/living/living_target = target
ADD_TRAIT(living_target, TRAIT_NO_TELEPORT, IMPLANT_TRAIT)
return TRUE
/obj/item/implant/exile/noteleport/removed(mob/target, silent = FALSE, special = FALSE)
. = ..()
if(!. || !isliving(target))
return FALSE
var/mob/living/living_target = target
REMOVE_TRAIT(living_target, TRAIT_NO_TELEPORT, IMPLANT_TRAIT)
return TRUE
/obj/item/implanter/exile
name = "implanter (exile)"
imp_type = /obj/item/implant/exile
/obj/item/implanter/exile/noteleport
name = "implanter (anti-teleportation)"
imp_type = /obj/item/implant/exile/noteleport
/obj/item/implantcase/exile
name = "implant case - 'Exile'"
desc = "A glass case containing an exile implant."
imp_type = /obj/item/implant/exile
/obj/item/implantcase/exile/noteleport
name = "implant case - 'Anti-Teleportation'"
desc = "A glass case containing an anti-teleportation implant."
imp_type = /obj/item/implant/exile/noteleport
@@ -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 = {"<b>Implant Specifications:</b><BR>
<b>Name:</b> Robust Corp EXP-001 'Bluespace Grounder'<BR>
<b>Implant Details:</b> 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.<BR>"}
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
@@ -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 = {"<b>Implant Specifications:</b><BR>
<b>Name:</b> Robust Corp EYE-5 Convict Parole Implant<BR>
<b>Life:</b> 10 minutes after death of host.<BR>
<HR>
<b>Implant Details:</b> <BR>
<b>Function:</b> Continuously transmits low power signal. Can be tracked from a prisoner management console.<BR>
<b>Special Features:</b><BR>
<i>Neuro-Safe</i>- 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.<BR>"}
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