From e46ac28416d4245b097bd0d07d03315632cd3363 Mon Sep 17 00:00:00 2001 From: Iajret Creature <122297233+Steals-The-PRs@users.noreply.github.com> Date: Mon, 11 Dec 2023 11:29:33 +0300 Subject: [PATCH] [MIRROR] "Security Implant" rework, prisoner management console updates [MDB IGNORE] (#25525) (#1061) * "Security Implant" rework, prisoner management console updates * Fix conflicts --------- Co-authored-by: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com> Co-authored-by: SomeRandomOwl Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> --- code/__DEFINES/atom_hud.dm | 4 +- code/__DEFINES/implants.dm | 5 + code/_globalvars/lists/objects.dm | 4 - code/game/data_huds.dm | 37 +-- .../machinery/computer/prisoner/_prisoner.dm | 74 ++--- .../machinery/computer/prisoner/management.dm | 206 ++++++------- code/game/machinery/computer/teleporter.dm | 12 +- code/game/objects/items/implants/implant.dm | 81 +++++- .../objects/items/implants/implant_track.dm | 59 ---- .../items/implants/security/implant_beacon.dm | 43 +++ .../implants/{ => security}/implant_chem.dm | 49 +++- .../implants/{ => security}/implant_exile.dm | 5 +- .../implants/security/implant_noteleport.dm | 45 +++ .../items/implants/security/implant_track.dm | 87 ++++++ .../items/storage/boxes/implant_boxes.dm | 82 ++++++ .../items/storage/boxes/security_boxes.dm | 54 ---- code/game/objects/items/teleportation.dm | 16 +- code/modules/cargo/packs/security.dm | 14 + code/modules/mining/mine_items.dm | 7 + .../mob/living/carbon/human/human_defines.dm | 2 +- .../projectiles/boxes_magazines/ammo_boxes.dm | 2 +- icons/mob/huds/hud.dmi | Bin 9339 -> 10246 bytes tgstation.dme | 10 +- .../tgui/interfaces/PrisonerManagement.tsx | 272 ++++++++++++++++++ 24 files changed, 847 insertions(+), 323 deletions(-) create mode 100644 code/__DEFINES/implants.dm delete mode 100644 code/game/objects/items/implants/implant_track.dm create mode 100644 code/game/objects/items/implants/security/implant_beacon.dm rename code/game/objects/items/implants/{ => security}/implant_chem.dm (62%) rename code/game/objects/items/implants/{ => security}/implant_exile.dm (93%) create mode 100644 code/game/objects/items/implants/security/implant_noteleport.dm create mode 100644 code/game/objects/items/implants/security/implant_track.dm create mode 100644 code/game/objects/items/storage/boxes/implant_boxes.dm create mode 100644 tgui/packages/tgui/interfaces/PrisonerManagement.tsx diff --git a/code/__DEFINES/atom_hud.dm b/code/__DEFINES/atom_hud.dm index 1bc91b38464..a38e1b793ab 100644 --- a/code/__DEFINES/atom_hud.dm +++ b/code/__DEFINES/atom_hud.dm @@ -14,9 +14,9 @@ /// loyality implant #define IMPLOYAL_HUD "5" /// chemical implant -#define IMPCHEM_HUD "6" +#define IMPSEC_FIRST_HUD "6" /// tracking implant -#define IMPTRACK_HUD "7" +#define IMPSEC_SECOND_HUD "7" /// Silicon/Mech/Circuit Status #define DIAG_STAT_HUD "8" /// Silicon health bar diff --git a/code/__DEFINES/implants.dm b/code/__DEFINES/implants.dm new file mode 100644 index 00000000000..3693526e1f3 --- /dev/null +++ b/code/__DEFINES/implants.dm @@ -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 diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm index 8239dc29231..1d53a499322 100644 --- a/code/_globalvars/lists/objects.dm +++ b/code/_globalvars/lists/objects.dm @@ -50,10 +50,6 @@ GLOBAL_LIST_EMPTY(cooking_recipes_atoms) GLOBAL_LIST_EMPTY(rcd_list) /// list of wallmounted intercom radios. 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. GLOBAL_LIST_EMPTY(pinpointer_list) /// A list of all zombie_infection organs, for any mass "animation" diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm index 043d9453908..62d19ccf9ab 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -47,7 +47,7 @@ hud_icons = list(ID_HUD) /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 hud_icons = list(FAN_HUD) @@ -287,25 +287,30 @@ Security HUDs! Basic mode shows only the job. /mob/living/proc/sec_hud_set_implants() 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.icon_state = null set_hud_image_inactive(i) - for(var/obj/item/implant/I in implants) - if(istype(I, /obj/item/implant/tracking)) - holder = hud_list[IMPTRACK_HUD] - var/icon/IC = icon(icon, icon_state, dir) - holder.pixel_y = IC.Height() - world.icon_size - holder.icon_state = "hud_imp_tracking" - set_hud_image_active(IMPTRACK_HUD) - - else if(istype(I, /obj/item/implant/chem)) - holder = hud_list[IMPCHEM_HUD] - var/icon/IC = icon(icon, icon_state, dir) - holder.pixel_y = IC.Height() - world.icon_size - holder.icon_state = "hud_imp_chem" - set_hud_image_active(IMPCHEM_HUD) + var/security_slot = 1 //Which of the two security hud slots are we putting found security implants in? + for(var/obj/item/implant/current_implant in implants) + if(current_implant.implant_flags & IMPLANT_TYPE_SECURITY) + switch(security_slot) + if(1) + holder = hud_list[IMPSEC_FIRST_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++ + + 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[IMPSEC_SECOND_HUD] + var/icon/IC = icon(icon, icon_state, dir) + holder.pixel_y = IC.Height() - world.icon_size + holder.pixel_x = initial(holder.pixel_x) + 7 //Adds an offset that mirrors the hud blip to the other side of the mob. + holder.icon_state = current_implant.hud_icon_state + set_hud_image_active(IMPSEC_SECOND_HUD) if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) holder = hud_list[IMPLOYAL_HUD] diff --git a/code/game/machinery/computer/prisoner/_prisoner.dm b/code/game/machinery/computer/prisoner/_prisoner.dm index 970b5f4f9be..16369302c66 100644 --- a/code/game/machinery/computer/prisoner/_prisoner.dm +++ b/code/game/machinery/computer/prisoner/_prisoner.dm @@ -1,53 +1,59 @@ /obj/machinery/computer/prisoner 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/deconstruct(disassembled, mob/user) + contained_id?.forceMove(drop_location()) + return ..() /obj/machinery/computer/prisoner/Destroy() - if(contained_id) - contained_id.forceMove(get_turf(src)) + 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) . = ..() if(contained_id) . += span_notice("Alt-click to eject the ID card.") - - /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) - if(istype(P)) - if(contained_id) - to_chat(user, span_warning("There's already an ID card in the console!")) - return - if(!user.transferItemToLoc(P, src)) - return - contained_id = P - user.visible_message(span_notice("[user] inserts an ID card into the console."), \ - span_notice("You insert the ID card into the console.")) - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) - updateUsrDialog() +/obj/machinery/computer/prisoner/proc/id_insert(mob/user, obj/item/card/id/advanced/prisoner/new_id) + if(!istype(new_id)) + return + if(!isnull(contained_id)) + balloon_alert(user, "no empty slot!") + return + if(!user.transferItemToLoc(new_id, src)) + return + contained_id = new_id + balloon_alert_to_viewers("id inserted") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) /obj/machinery/computer/prisoner/proc/id_eject(mob/user) - if(!contained_id) - to_chat(user, span_warning("There's no ID card in the console!")) + if(isnull(contained_id)) + balloon_alert(user, "no id!") return + + if(!issilicon(user) && Adjacent(user)) + user.put_in_hands(contained_id) else 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) - if(istype(I, /obj/item/card/id/advanced/prisoner)) - id_insert(user, I) - else - return ..() + + balloon_alert_to_viewers("id ejected") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + +/obj/machinery/computer/prisoner/attackby(obj/item/weapon, mob/user, params) + if(istype(weapon, /obj/item/card/id/advanced/prisoner)) + id_insert(user, weapon) + return TRUE + + return ..() diff --git a/code/game/machinery/computer/prisoner/management.dm b/code/game/machinery/computer/prisoner/management.dm index 7acac9b6610..ada71bad023 100644 --- a/code/game/machinery/computer/prisoner/management.dm +++ b/code/game/machinery/computer/prisoner/management.dm @@ -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 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_keyboard = "security_key" req_access = list(ACCESS_BRIG) 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 +/obj/machinery/computer/prisoner/management/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "PrisonerManagement") + ui.open() + +/obj/machinery/computer/prisoner/management/ui_data(mob/user) + var/list/data = list() + + data["authorized"] = (authenticated && isliving(user)) || isAdminGhostAI(user) || issilicon(user) + data["inserted_id"] = null + if(!isnull(contained_id)) + data["inserted_id"] = list( + "name" = contained_id.name, + "points" = contained_id.points, + "goal" = contained_id.goal, + ) + + var/list/implants = list() + for(var/obj/item/implant/implant as anything in GLOB.tracked_implants) + if(!implant.is_shown_on_console(src)) + continue + var/list/implant_data = list() + implant_data["info"] = implant.get_management_console_data() + implant_data["buttons"] = implant.get_management_console_buttons() + 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/ui_interact(mob/user) + return data + +/obj/machinery/computer/prisoner/management/ui_act(action, list/params) . = ..() - if(isliving(user)) - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - var/dat = "" - if(screen == 0) - dat += "
{Log In}" - else if(screen == 1) - dat += "

Prisoner ID Management

" - 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 d71ba4b0940a677b549f6f5a6a1072a98147ccc0..185273784733d70795ada434638f45747d448e0f 100644 GIT binary patch literal 10246 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 literal 9339 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 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 ? : } + + + ); +};