[MIRROR] "Security Implant" rework, prisoner management console updates [MDB IGNORE] (#25525)

* "Security Implant" rework, prisoner management console updates

* Fix conflicts

---------

Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com>
Co-authored-by: SomeRandomOwl <somerandomowl@ratchtnet.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
This commit is contained in:
SkyratBot
2023-12-10 05:49:54 +01:00
committed by GitHub
parent 0f3ee0c7ce
commit d143f17ccd
24 changed files with 838 additions and 314 deletions

View File

@@ -14,9 +14,9 @@
/// loyality implant /// loyality implant
#define IMPLOYAL_HUD "5" #define IMPLOYAL_HUD "5"
/// chemical implant /// chemical implant
#define IMPCHEM_HUD "6" #define IMPSEC_FIRST_HUD "6"
/// tracking implant /// tracking implant
#define IMPTRACK_HUD "7" #define IMPSEC_SECOND_HUD "7"
/// Silicon/Mech/Circuit Status /// Silicon/Mech/Circuit Status
#define DIAG_STAT_HUD "8" #define DIAG_STAT_HUD "8"
/// Silicon health bar /// Silicon health bar

View File

@@ -0,0 +1,5 @@
///Denotes this as a "security" implant, limiting it to two per body
#define IMPLANT_TYPE_SECURITY (1<<0)
///Maximum number of security implants in a mob at once.
#define SECURITY_IMPLANT_CAP 2

View File

@@ -50,10 +50,6 @@ GLOBAL_LIST_EMPTY(cooking_recipes_atoms)
GLOBAL_LIST_EMPTY(rcd_list) GLOBAL_LIST_EMPTY(rcd_list)
/// list of wallmounted intercom radios. /// list of wallmounted intercom radios.
GLOBAL_LIST_EMPTY(intercoms_list) GLOBAL_LIST_EMPTY(intercoms_list)
/// list of all current implants that are tracked to work out what sort of trek everyone is on. Sadly not on lavaworld not implemented...
GLOBAL_LIST_EMPTY(tracked_implants)
/// list of implants the prisoner console can track and send inject commands too
GLOBAL_LIST_EMPTY(tracked_chem_implants)
/// list of all pinpointers. Used to change stuff they are pointing to all at once. /// list of all pinpointers. Used to change stuff they are pointing to all at once.
GLOBAL_LIST_EMPTY(pinpointer_list) GLOBAL_LIST_EMPTY(pinpointer_list)
/// A list of all zombie_infection organs, for any mass "animation" /// A list of all zombie_infection organs, for any mass "animation"

View File

@@ -47,7 +47,7 @@
hud_icons = list(ID_HUD) hud_icons = list(ID_HUD)
/datum/atom_hud/data/human/security/advanced /datum/atom_hud/data/human/security/advanced
hud_icons = list(ID_HUD, IMPTRACK_HUD, IMPLOYAL_HUD, IMPCHEM_HUD, WANTED_HUD, PERMIT_HUD, DNR_HUD) //SKYRAT EDIT ADDITION - PERMIT_HUD, DNR_HUD hud_icons = list(ID_HUD, IMPSEC_FIRST_HUD, IMPLOYAL_HUD, IMPSEC_SECOND_HUD, WANTED_HUD, PERMIT_HUD, DNR_HUD) //SKYRAT EDIT ADDITION - PERMIT_HUD, DNR_HUD
/datum/atom_hud/data/human/fan_hud /datum/atom_hud/data/human/fan_hud
hud_icons = list(FAN_HUD) hud_icons = list(FAN_HUD)
@@ -287,25 +287,30 @@ Security HUDs! Basic mode shows only the job.
/mob/living/proc/sec_hud_set_implants() /mob/living/proc/sec_hud_set_implants()
var/image/holder var/image/holder
for(var/i in list(IMPTRACK_HUD, IMPLOYAL_HUD, IMPCHEM_HUD)) for(var/i in list(IMPSEC_FIRST_HUD, IMPLOYAL_HUD, IMPSEC_SECOND_HUD))
holder = hud_list[i] holder = hud_list[i]
holder.icon_state = null holder.icon_state = null
set_hud_image_inactive(i) set_hud_image_inactive(i)
for(var/obj/item/implant/I in implants) var/security_slot = 1 //Which of the two security hud slots are we putting found security implants in?
if(istype(I, /obj/item/implant/tracking)) for(var/obj/item/implant/current_implant in implants)
holder = hud_list[IMPTRACK_HUD] if(current_implant.implant_flags & IMPLANT_TYPE_SECURITY)
var/icon/IC = icon(icon, icon_state, dir) switch(security_slot)
holder.pixel_y = IC.Height() - world.icon_size if(1)
holder.icon_state = "hud_imp_tracking" holder = hud_list[IMPSEC_FIRST_HUD]
set_hud_image_active(IMPTRACK_HUD) var/icon/IC = icon(icon, icon_state, dir)
holder.pixel_y = IC.Height() - world.icon_size
holder.icon_state = current_implant.hud_icon_state
set_hud_image_active(IMPSEC_FIRST_HUD)
security_slot++
else if(istype(I, /obj/item/implant/chem)) if(2) //Theoretically if we somehow get multiple sec implants, whatever the most recently implanted implant is will take over the 2nd position
holder = hud_list[IMPCHEM_HUD] holder = hud_list[IMPSEC_SECOND_HUD]
var/icon/IC = icon(icon, icon_state, dir) var/icon/IC = icon(icon, icon_state, dir)
holder.pixel_y = IC.Height() - world.icon_size holder.pixel_y = IC.Height() - world.icon_size
holder.icon_state = "hud_imp_chem" holder.pixel_x = initial(holder.pixel_x) + 7 //Adds an offset that mirrors the hud blip to the other side of the mob.
set_hud_image_active(IMPCHEM_HUD) holder.icon_state = current_implant.hud_icon_state
set_hud_image_active(IMPSEC_SECOND_HUD)
if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) if(HAS_TRAIT(src, TRAIT_MINDSHIELD))
holder = hud_list[IMPLOYAL_HUD] holder = hud_list[IMPLOYAL_HUD]

View File

@@ -1,53 +1,59 @@
/obj/machinery/computer/prisoner /obj/machinery/computer/prisoner
interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_SET_MACHINE|INTERACT_MACHINE_REQUIRES_LITERACY interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_SET_MACHINE|INTERACT_MACHINE_REQUIRES_LITERACY
var/obj/item/card/id/advanced/prisoner/contained_id /// ID card currently inserted into the computer.
VAR_FINAL/obj/item/card/id/advanced/prisoner/contained_id
/obj/machinery/computer/prisoner/Destroy() /obj/machinery/computer/prisoner/deconstruct(disassembled, mob/user)
if(contained_id) contained_id?.forceMove(drop_location())
contained_id.forceMove(get_turf(src))
return ..() return ..()
/obj/machinery/computer/prisoner/Destroy()
QDEL_NULL(contained_id)
return ..()
/obj/machinery/computer/prisoner/Exited(atom/movable/gone, direction)
. = ..()
if(gone == contained_id)
contained_id = null
/obj/machinery/computer/prisoner/examine(mob/user) /obj/machinery/computer/prisoner/examine(mob/user)
. = ..() . = ..()
if(contained_id) if(contained_id)
. += span_notice("<b>Alt-click</b> to eject the ID card.") . += span_notice("<b>Alt-click</b> to eject the ID card.")
/obj/machinery/computer/prisoner/AltClick(mob/user) /obj/machinery/computer/prisoner/AltClick(mob/user)
id_eject(user) . = ..()
return ..() if(user.can_perform_action(src, ALLOW_SILICON_REACH))
id_eject(user)
/obj/machinery/computer/prisoner/proc/id_insert(mob/user, obj/item/card/id/advanced/prisoner/P) /obj/machinery/computer/prisoner/proc/id_insert(mob/user, obj/item/card/id/advanced/prisoner/new_id)
if(istype(P)) if(!istype(new_id))
if(contained_id) return
to_chat(user, span_warning("There's already an ID card in the console!")) if(!isnull(contained_id))
return balloon_alert(user, "no empty slot!")
if(!user.transferItemToLoc(P, src)) return
return if(!user.transferItemToLoc(new_id, src))
contained_id = P return
user.visible_message(span_notice("[user] inserts an ID card into the console."), \ contained_id = new_id
span_notice("You insert the ID card into the console.")) balloon_alert_to_viewers("id inserted")
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
updateUsrDialog()
/obj/machinery/computer/prisoner/proc/id_eject(mob/user) /obj/machinery/computer/prisoner/proc/id_eject(mob/user)
if(!contained_id) if(isnull(contained_id))
to_chat(user, span_warning("There's no ID card in the console!")) balloon_alert(user, "no id!")
return return
if(!issilicon(user) && Adjacent(user))
user.put_in_hands(contained_id)
else else
contained_id.forceMove(drop_location()) contained_id.forceMove(drop_location())
if(!issilicon(user) && Adjacent(user))
user.put_in_hands(contained_id)
contained_id = null
user.visible_message(span_notice("[user] gets an ID card from the console."), \
span_notice("You get the ID card from the console."))
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
updateUsrDialog()
/obj/machinery/computer/prisoner/attackby(obj/item/I, mob/user) balloon_alert_to_viewers("id ejected")
if(istype(I, /obj/item/card/id/advanced/prisoner)) playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
id_insert(user, I)
else /obj/machinery/computer/prisoner/attackby(obj/item/weapon, mob/user, params)
return ..() if(istype(weapon, /obj/item/card/id/advanced/prisoner))
id_insert(user, weapon)
return TRUE
return ..()

View File

@@ -1,137 +1,97 @@
/// List of all implants currently implanted into a mob
GLOBAL_LIST_EMPTY_TYPED(tracked_implants, /obj/item/implant)
/obj/machinery/computer/prisoner/management /obj/machinery/computer/prisoner/management
name = "prisoner management console" name = "prisoner management console"
desc = "Used to manage tracking implants placed inside criminals." desc = "Used to modify prisoner IDs, as well as manage security implants placed inside convicts and parolees."
icon_screen = "explosive" icon_screen = "explosive"
icon_keyboard = "security_key" icon_keyboard = "security_key"
req_access = list(ACCESS_BRIG) req_access = list(ACCESS_BRIG)
light_color = COLOR_SOFT_RED light_color = COLOR_SOFT_RED
var/id = 0
var/temp = null
var/status = 0
var/timeleft = 60
var/stop = 0
var/screen = 0 // 0 - No Access Denied, 1 - Access allowed
circuit = /obj/item/circuitboard/computer/prisoner circuit = /obj/item/circuitboard/computer/prisoner
/obj/machinery/computer/prisoner/management/ui_interact(mob/user, datum/tgui/ui)
/obj/machinery/computer/prisoner/management/ui_interact(mob/user)
. = ..() . = ..()
if(isliving(user)) ui = SStgui.try_update_ui(user, src, ui)
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) if(!ui)
var/dat = "" ui = new(user, src, "PrisonerManagement")
if(screen == 0) ui.open()
dat += "<HR><A href='?src=[REF(src)];lock=1'>{Log In}</A>"
else if(screen == 1)
dat += "<H3>Prisoner ID Management</H3>"
if(contained_id)
dat += "<A href='?src=[REF(src)];id=eject'>[contained_id]</A><br>"
dat += "Collected Points: [contained_id.points]. <A href='?src=[REF(src)];id=reset'>Reset.</A><br>"
dat += "Card goal: [contained_id.goal]. <A href='?src=[REF(src)];id=setgoal'>Set </A><br>"
dat += "Space Law recommends quotas of 100 points per minute they would normally serve in the brig.<BR>"
else
dat += "<A href='?src=[REF(src)];id=insert'>Insert Prisoner ID.</A><br>"
dat += "<H3>Prisoner Implant Management</H3>"
dat += "<HR>Chemical Implants<BR>"
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] <BR>"
dat += "| Inject: "
dat += "<A href='?src=[REF(src)];inject1=[REF(C)]'>(<font class='bad'>(1)</font>)</A>"
dat += "<A href='?src=[REF(src)];inject5=[REF(C)]'>(<font class='bad'>(5)</font>)</A>"
dat += "<A href='?src=[REF(src)];inject10=[REF(C)]'>(<font class='bad'>(10)</font>)</A><BR>"
dat += "********************************<BR>"
dat += "<HR>Tracking Implants<BR>"
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" /obj/machinery/computer/prisoner/management/ui_data(mob/user)
var/mob/living/M = T.imp_in var/list/data = list()
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]<BR>" data["authorized"] = (authenticated && isliving(user)) || isAdminGhostAI(user) || issilicon(user)
dat += "<A href='?src=[REF(src)];warn=[REF(T)]'>(<font class='bad'><i>Message Holder</i></font>)</A> |<BR>" data["inserted_id"] = null
dat += "********************************<BR>" if(!isnull(contained_id))
dat += "<HR><A href='?src=[REF(src)];lock=1'>{Log Out}</A>" data["inserted_id"] = list(
var/datum/browser/popup = new(user, "computer", "Prisoner Management Console", 400, 500) "name" = contained_id.name,
popup.set_content(dat) "points" = contained_id.points,
popup.open() "goal" = contained_id.goal,
return )
/obj/machinery/computer/prisoner/management/attackby(obj/item/I, mob/user, params) var/list/implants = list()
if(isidcard(I)) for(var/obj/item/implant/implant as anything in GLOB.tracked_implants)
if(screen) if(!implant.is_shown_on_console(src))
id_insert(user) continue
else var/list/implant_data = list()
to_chat(user, span_danger("Unauthorized access.")) implant_data["info"] = implant.get_management_console_data()
else implant_data["buttons"] = implant.get_management_console_buttons()
return ..() implant_data["category"] = initial(implant.name)
implant_data["ref"] = REF(implant)
UNTYPED_LIST_ADD(implants, implant_data)
data["implants"] = implants
/obj/machinery/computer/prisoner/management/process() return data
if(!..())
src.updateDialog()
return
/obj/machinery/computer/prisoner/management/Topic(href, href_list) /obj/machinery/computer/prisoner/management/ui_act(action, list/params)
if(..()) . = ..()
if(.)
return return
if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr))
usr.set_machine(src)
if(href_list["id"]) if(!authenticated && action != "login")
if(href_list["id"] == "insert" && !contained_id) CRASH("[usr] potentially spoofed ui action [action] on prisoner console without the console being logged in.")
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(isliving(usr))
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
switch(action)
if("login")
if(allowed(usr)) if(allowed(usr))
screen = !screen authenticated = TRUE
playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE)
else else
to_chat(usr, span_danger("Unauthorized access.")) playsound(src, 'sound/machines/terminal_error.ogg', 50, FALSE)
return TRUE
else if(href_list["warn"]) if("logout")
var/warning = tgui_input_text(usr, "Enter your message here", "Messaging") authenticated = FALSE
if(!warning) playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE)
return return TRUE
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) if("insert_id")
src.updateUsrDialog() id_insert(usr, usr.get_active_held_item())
return 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

View File

@@ -167,16 +167,16 @@
var/area/area = get_area(beacon) var/area/area = get_area(beacon)
targets[avoid_assoc_duplicate_keys(format_text(area.name), area_index)] = 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) for (var/obj/item/implant/beacon/tracking_beacon in GLOB.tracked_implants)
if (!tracking_implant.imp_in || !isliving(tracking_implant.loc) || !tracking_implant.allow_teleport) if (isnull(tracking_beacon.imp_in) || !isliving(tracking_beacon.loc))
continue continue
var/mob/living/implanted = tracking_implant.loc var/mob/living/implanted = tracking_beacon.loc
if (implanted.stat == DEAD && implanted.timeofdeath + tracking_implant.lifespan_postmortem < world.time) if (implanted.stat == DEAD && implanted.timeofdeath + tracking_beacon.lifespan_postmortem < world.time)
continue continue
if (is_eligible(tracking_implant)) if (is_eligible(tracking_beacon))
targets[avoid_assoc_duplicate_keys("[implanted.real_name] ([format_text(get_area(implanted))])", area_index)] = tracking_implant targets[avoid_assoc_duplicate_keys("[implanted.real_name] ([format_text(get_area(implanted))])", area_index)] = tracking_beacon
else else
for (var/obj/machinery/teleport/station/station as anything in power_station.linked_stations) for (var/obj/machinery/teleport/station/station as anything in power_station.linked_stations)
if (is_eligible(station) && station.teleporter_hub) if (is_eligible(station) && station.teleporter_hub)

View File

@@ -19,6 +19,10 @@
var/allow_multiple = FALSE var/allow_multiple = FALSE
///how many times this can do something, only relevant for implants with limited uses ///how many times this can do something, only relevant for implants with limited uses
var/uses = -1 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() /obj/item/implant/proc/activate()
@@ -60,12 +64,17 @@
if(!force && !can_be_implanted_in(target)) if(!force && !can_be_implanted_in(target))
return FALSE return FALSE
for(var/X in target.implants) var/security_implants = 0 //Used to track how many implants with the "security" flag are in the user.
var/obj/item/implant/other_implant = X for(var/obj/item/implant/other_implant as anything in target.implants)
var/flags = SEND_SIGNAL(other_implant, COMSIG_IMPLANT_OTHER, args, src) var/flags = SEND_SIGNAL(other_implant, COMSIG_IMPLANT_OTHER, args, src)
if(flags & COMPONENT_STOP_IMPLANTING) if(flags & COMPONENT_STOP_IMPLANTING)
UNSETEMPTY(target.implants) UNSETEMPTY(target.implants)
return FALSE 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) if(flags & COMPONENT_DELETE_NEW_IMPLANT)
UNSETEMPTY(target.implants) UNSETEMPTY(target.implants)
qdel(src) qdel(src)
@@ -100,6 +109,7 @@
log_combat(user, target, "implanted", "\a [name]") log_combat(user, target, "implanted", "\a [name]")
SEND_SIGNAL(src, COMSIG_IMPLANT_IMPLANTED, target, user, silent, force) SEND_SIGNAL(src, COMSIG_IMPLANT_IMPLANTED, target, user, silent, force)
GLOB.tracked_implants += src
return TRUE return TRUE
/** /**
@@ -122,12 +132,14 @@
human_source.sec_hud_set_implants() human_source.sec_hud_set_implants()
SEND_SIGNAL(src, COMSIG_IMPLANT_REMOVED, source, silent, special) SEND_SIGNAL(src, COMSIG_IMPLANT_REMOVED, source, silent, special)
GLOB.tracked_implants -= src
return TRUE return TRUE
/obj/item/implant/Destroy() /obj/item/implant/Destroy()
if(imp_in) if(imp_in)
removed(imp_in) removed(imp_in)
return ..() return ..()
/** /**
* Gets implant specifications for the implant pad * Gets implant specifications for the implant pad
*/ */
@@ -137,3 +149,68 @@
/obj/item/implant/dropped(mob/user) /obj/item/implant/dropped(mob/user)
. = TRUE . = 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

View File

@@ -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 = {"<b>Implant Specifications:</b><BR>
<b>Name:</b> Tracking Beacon<BR>
<b>Life:</b> 10 minutes after death of host.<BR>
<b>Important Notes:</b> Implant [allow_teleport ? "also works" : "does not work"] as a teleporter beacon.<BR>
<HR>
<b>Implant Details:</b> <BR>
<b>Function:</b> Continuously transmits low power signal. Useful for tracking.<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>
<b>Integrity:</b> Gradient creates slight risk of being overcharged and frying the
circuitry. As a result neurotoxins can cause massive damage."}
return dat

View File

@@ -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

View File

@@ -3,6 +3,10 @@
desc = "Injects things." desc = "Injects things."
icon_state = "reagents" icon_state = "reagents"
actions_types = null 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() /obj/item/implant/chem/get_data()
var/dat = {"<b>Implant Specifications:</b><BR> var/dat = {"<b>Implant Specifications:</b><BR>
@@ -10,24 +14,51 @@
<b>Life:</b> Deactivates upon death but remains within the body.<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> <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> will suffer from an increased appetite.</B><BR>
<HR>
<b>Implant Details:</b><BR> <b>Implant Details:</b><BR>
<b>Function:</b> Contains a small capsule that can contain various chemicals. Upon receiving a specially encoded signal<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> the implant releases the chemicals directly into the blood stream.<BR>
<b>Special Features:</b>
<i>Micro-Capsule</i>- Can be loaded with any sort of chemical agent via the common syringe and can hold 50 units.<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> Can only be loaded while still in its original case.<BR>
<b>Integrity:</b> Implant will last so long as the subject is alive."} <b>Integrity:</b> Implant will last so long as the subject is alive, breaking down and releasing all contents on death."}
return dat 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) /obj/item/implant/chem/Initialize(mapload)
. = ..() . = ..()
create_reagents(50, OPENCONTAINER) 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) /obj/item/implant/chem/implant(mob/living/target, mob/user, silent = FALSE, force = FALSE)
. = ..() . = ..()

View File

@@ -5,11 +5,14 @@
name = "exile implant" name = "exile implant"
desc = "Prevents you from returning from away missions." desc = "Prevents you from returning from away missions."
actions_types = null actions_types = null
implant_flags = IMPLANT_TYPE_SECURITY
hud_icon_state = "hud_imp_exile"
/obj/item/implant/exile/get_data() /obj/item/implant/exile/get_data()
var/dat = {"<b>Implant Specifications:</b><BR> var/dat = {"<b>Implant Specifications:</b><BR>
<b>Name:</b> Nanotrasen Employee Exile Implant<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.<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 return dat

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -69,60 +69,6 @@
for(var/i in 1 to 5) for(var/i in 1 to 5)
new /obj/item/grenade/empgrenade(src) 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 /obj/item/storage/box/prisoner
name = "box of prisoner IDs" name = "box of prisoner IDs"
desc = "Take away their last shred of dignity, their name." desc = "Take away their last shred of dignity, their name."

View File

@@ -24,7 +24,7 @@
throw_speed = 3 throw_speed = 3
throw_range = 7 throw_range = 7
custom_materials = list(/datum/material/iron= SMALL_MATERIAL_AMOUNT * 4) 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) /obj/item/locator/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui) ui = SStgui.try_update_ui(user, src, ui)
@@ -72,22 +72,22 @@
var/list/track_implants = list() var/list/track_implants = list()
for (var/obj/item/implant/tracking/W in GLOB.tracked_implants) for (var/obj/item/implant/beacon/tracking_beacon in GLOB.tracked_implants)
if (!W.imp_in || !isliving(W.loc)) if (!tracking_beacon.imp_in || !isliving(tracking_beacon.loc))
continue continue
else else
var/mob/living/M = W.loc var/mob/living/living_mob = tracking_beacon.loc
if (M.stat == DEAD) if (living_mob.stat == DEAD)
if (M.timeofdeath + W.lifespan_postmortem < world.time) if (living_mob.timeofdeath + tracking_beacon.lifespan_postmortem < world.time)
continue 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)) var/distance = max(abs(tr.x - sr.x), abs(tr.y - sr.y))
if(distance > tracking_range) if(distance > tracking_range)
continue continue
var/D = dir2text(get_dir(sr, tr)) 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 data["trackimplants"] = track_implants
return data return data

View File

@@ -340,3 +340,17 @@
access_view = ACCESS_SECURITY access_view = ACCESS_SECURITY
contains = list(/obj/item/clothing/glasses/sunglasses = 1) contains = list(/obj/item/clothing/glasses/sunglasses = 1)
crate_name = "sunglasses crate" 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"

View File

@@ -103,6 +103,13 @@
if (HAS_TRAIT(user, TRAIT_FORBID_MINING_SHUTTLE_CONSOLE_OUTSIDE_STATION) && !is_station_level(user.z)) 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.")) to_chat(user, span_warning("You get the feeling you shouldn't mess with this."))
return 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 ..() return ..()
/obj/machinery/computer/shuttle/mining/common /obj/machinery/computer/shuttle/mining/common

View File

@@ -5,7 +5,7 @@
icon = 'icons/mob/human/human.dmi' icon = 'icons/mob/human/human.dmi'
icon_state = "human_basic" icon_state = "human_basic"
appearance_flags = KEEP_TOGETHER|TILE_BOUND|PIXEL_SCALE|LONG_GLIDE 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 hud_type = /datum/hud/human
pressure_resistance = 25 pressure_resistance = 25
can_buckle = TRUE can_buckle = TRUE

View File

@@ -42,7 +42,7 @@
/obj/item/ammo_box/c38/trac /obj/item/ammo_box/c38/trac
name = "speed loader (.38 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_type = /obj/item/ammo_casing/c38/trac
ammo_band_color = "#7b6383" ammo_band_color = "#7b6383"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -109,6 +109,7 @@
#include "code\__DEFINES\hud.dm" #include "code\__DEFINES\hud.dm"
#include "code\__DEFINES\icon_smoothing.dm" #include "code\__DEFINES\icon_smoothing.dm"
#include "code\__DEFINES\id_cards.dm" #include "code\__DEFINES\id_cards.dm"
#include "code\__DEFINES\implants.dm"
#include "code\__DEFINES\important_recursive_contents.dm" #include "code\__DEFINES\important_recursive_contents.dm"
#include "code\__DEFINES\injection.dm" #include "code\__DEFINES\injection.dm"
#include "code\__DEFINES\input.dm" #include "code\__DEFINES\input.dm"
@@ -2497,10 +2498,8 @@
#include "code\game\objects\items\grenades\syndieminibomb.dm" #include "code\game\objects\items\grenades\syndieminibomb.dm"
#include "code\game\objects\items\implants\implant.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_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_clown.dm"
#include "code\game\objects\items\implants\implant_deathrattle.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_explosive.dm"
#include "code\game\objects\items\implants\implant_freedom.dm" #include "code\game\objects\items\implants\implant_freedom.dm"
#include "code\game\objects\items\implants\implant_krav_maga.dm" #include "code\game\objects\items\implants\implant_krav_maga.dm"
@@ -2509,12 +2508,16 @@
#include "code\game\objects\items\implants\implant_spell.dm" #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_stealth.dm"
#include "code\game\objects\items\implants\implant_storage.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\implantcase.dm"
#include "code\game\objects\items\implants\implantchair.dm" #include "code\game\objects\items\implants\implantchair.dm"
#include "code\game\objects\items\implants\implanter.dm" #include "code\game\objects\items\implants\implanter.dm"
#include "code\game\objects\items\implants\implantpad.dm" #include "code\game\objects\items\implants\implantpad.dm"
#include "code\game\objects\items\implants\implantuplink.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\kirbyplants.dm"
#include "code\game\objects\items\kirby_plants\organic_plants.dm" #include "code\game\objects\items\kirby_plants\organic_plants.dm"
#include "code\game\objects\items\kirby_plants\synthetic_plants.dm" #include "code\game\objects\items\kirby_plants\synthetic_plants.dm"
@@ -2587,6 +2590,7 @@
#include "code\game\objects\items\storage\boxes\clothes_boxes.dm" #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\engineering_boxes.dm"
#include "code\game\objects\items\storage\boxes\food_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\job_boxes.dm"
#include "code\game\objects\items\storage\boxes\medical_boxes.dm" #include "code\game\objects\items\storage\boxes\medical_boxes.dm"
#include "code\game\objects\items\storage\boxes\science_boxes.dm" #include "code\game\objects\items\storage\boxes\science_boxes.dm"

View File

@@ -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<string, string>;
};
type ImplantInfo = {
info: Record<string, string>;
buttons: DMButton[];
category: string;
ref: byondRef;
};
type Data = {
authorized: BooleanLike;
inserted_id: IDInfo | null;
implants: ImplantInfo[];
};
const ImplantDisplay = (props: { implant: ImplantInfo }) => {
const { act } = useBackend<ImplantInfo>();
const { info, buttons, ref } = props.implant;
return (
<Stack vertical>
<Stack.Item>
<LabeledList>
{Object.entries(info).map(([key, value]) => (
<LabeledList.Item key={key} label={key}>
{value}
</LabeledList.Item>
))}
{buttons.length !== 0 && (
<LabeledList.Item label={'Options'}>
{buttons.map((button) => (
<Button
key={button.action_key}
icon={button.icon}
color={button.color}
tooltip={button.tooltip}
onClick={() =>
act('handle_implant', {
implant_ref: ref,
implant_action: button.action_key,
...button.action_params,
})
}
>
{button.name}
</Button>
))}
</LabeledList.Item>
)}
<LabeledList.Divider />
</LabeledList>
</Stack.Item>
</Stack>
);
};
// When given a list of implants, sorts them by category
const sortImplants = (implants: ImplantInfo[]) => {
const implantsByCategory: Record<string, ImplantInfo[]> = 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<string, ImplantInfo[]> = sortImplants(
props.implants,
);
const [implantTab, setImplantTab] = useSharedState(
'implantTab',
Object.keys(implantsByCategory)[0],
);
return (
<Stack fill vertical>
<Stack.Item>
<Tabs>
{Object.entries(implantsByCategory).map(([category, implants]) => (
<Tabs.Tab
key={category}
selected={implantTab === category}
onClick={() => setImplantTab(category)}
>
{formatCategory(category)}
</Tabs.Tab>
))}
</Tabs>
</Stack.Item>
<Stack.Item>
{implantTab && implantsByCategory && implantsByCategory[implantTab] ? (
implantsByCategory[implantTab].map((implant) => (
<ImplantDisplay key={implant.ref} implant={implant} />
))
) : (
<NoticeBox>No implants detected.</NoticeBox>
)}
</Stack.Item>
</Stack>
);
};
const IdShowcase = (props: { id: IDInfo | null }) => {
const { act } = useBackend<IDInfo>();
const { id } = props;
return (
<Stack fill vertical>
<Stack.Item>
<LabeledList>
{id ? (
<>
<LabeledList.Item label="ID">
<Button onClick={() => act('eject_id')} icon="eject" mr={1} />
{id.name}
</LabeledList.Item>
<LabeledList.Item label="Points">
<Button
onClick={() => act('reset_id')}
icon="times"
color="bad"
mr={1}
/>
{id.points}
</LabeledList.Item>
<LabeledList.Item label="Goal">
<Button
onClick={() => act('set_id_goal')}
icon="check"
mr={1}
/>
{id.goal}
</LabeledList.Item>
</>
) : (
<LabeledList.Item label="ID">
<Button onClick={() => act('insert_id')}>No ID Inserted</Button>
</LabeledList.Item>
)}
</LabeledList>
</Stack.Item>
{!!id && (
<Stack.Item>
<NoticeBox>
Space Law recommends quotas of 100 points per minute they would
normally serve in the brig.
</NoticeBox>
</Stack.Item>
)}
</Stack>
);
};
const ManagementConsole = () => {
const { act, data } = useBackend<Data>();
return (
<Stack fill vertical>
<Stack.Item>
<Section title="ID Management">
<IdShowcase id={data.inserted_id} />
</Section>
</Stack.Item>
<Stack.Item grow>
<Section title="Security Implants" scrollable fill>
<AllImplantDisplay implants={data.implants} />
</Section>
</Stack.Item>
<Stack.Item>
<NoticeBox align="right" info>
Secure Your Workspace.
<Button
align="right"
icon="lock"
color="good"
ml={2}
onClick={() => act('logout')}
>
Log Out
</Button>
</NoticeBox>
</Stack.Item>
</Stack>
);
};
// I copied this from security records,
// should probably make this a generic component
const LogIn = () => {
const { act } = useBackend<Data>();
return (
<Section fill>
<Stack fill vertical>
<Stack.Item grow />
<Stack.Item align="center" grow={2}>
<Icon color="average" name="exclamation-triangle" size={15} />
</Stack.Item>
<Stack.Item align="center" grow>
<Box color="red" fontSize="18px" bold mt={5}>
Nanotrasen SecurityHUB
</Box>
</Stack.Item>
<Stack.Item>
<NoticeBox align="right">
You are not logged in.
<Button ml={2} icon="lock-open" onClick={() => act('login')}>
Login
</Button>
</NoticeBox>
</Stack.Item>
</Stack>
</Section>
);
};
export const PrisonerManagement = () => {
const { data } = useBackend<Data>();
const { authorized } = data;
return (
<Window width={465} height={565} title="Prisoner Management">
<Window.Content>
{authorized ? <ManagementConsole /> : <LogIn />}
</Window.Content>
</Window>
);
};