diff --git a/code/__DEFINES/dcs/signals/signals_key.dm b/code/__DEFINES/dcs/signals/signals_key.dm new file mode 100644 index 00000000000..58448562cb2 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_key.dm @@ -0,0 +1,2 @@ +///Called when a keycard is sending a department's access. (obj/machinery/keycard_auth/source, list/region_access) +#define COMSIG_ON_DEPARTMENT_ACCESS "on_department_access" diff --git a/code/controllers/subsystem/id_access.dm b/code/controllers/subsystem/id_access.dm index 958ec2324fc..96c0f3336d0 100644 --- a/code/controllers/subsystem/id_access.dm +++ b/code/controllers/subsystem/id_access.dm @@ -159,7 +159,7 @@ SUBSYSTEM_DEF(id_access) "pdas" = list(), ), "[ACCESS_HOP]" = list( - "regions" = list(REGION_GENERAL, REGION_SUPPLY), + "regions" = list(REGION_GENERAL), "head" = JOB_HEAD_OF_PERSONNEL, "templates" = list(), "pdas" = list(), diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 020a79ab096..6c4927612d7 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -283,13 +283,16 @@ try_to_crowbar(tool, user, forced_open) return TOOL_ACT_TOOLTYPE_SUCCESS -/obj/machinery/door/attackby(obj/item/I, mob/living/user, params) - if(!user.combat_mode && istype(I, /obj/item/fireaxe)) - try_to_crowbar(I, user, FALSE) +/obj/machinery/door/attackby(obj/item/weapon, mob/living/user, params) + if(istype(weapon, /obj/item/access_key)) + var/obj/item/access_key/key = weapon + return key.attempt_open_door(user, src) + else if(!user.combat_mode && istype(weapon, /obj/item/fireaxe)) + try_to_crowbar(weapon, user, FALSE) return TRUE - else if(I.item_flags & NOBLUDGEON || user.combat_mode) + else if(weapon.item_flags & NOBLUDGEON || user.combat_mode) return ..() - else if(!user.combat_mode && istype(I, /obj/item/stack/sheet/mineral/wood)) + else if(!user.combat_mode && istype(weapon, /obj/item/stack/sheet/mineral/wood)) return ..() // we need this so our can_barricade element can be called using COMSIG_PARENT_ATTACKBY else if(try_to_activate_door(user)) return TRUE diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 5a4e990e7a9..92dca169acc 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -16,7 +16,7 @@ GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/e !!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!! */ - ///icon state for inhand overlays, if null the normal icon_state will be used. + ///icon state for inhand overlays. var/inhand_icon_state = null ///Icon file for left hand inhand overlays var/lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' diff --git a/code/game/objects/items/janitor_key.dm b/code/game/objects/items/janitor_key.dm new file mode 100644 index 00000000000..6717c5388ec --- /dev/null +++ b/code/game/objects/items/janitor_key.dm @@ -0,0 +1,82 @@ +///The time limit on the keys before the access it's been given clears itself. +#define ACCESS_TIMER_LIMIT (10 MINUTES) + +/obj/item/access_key + name = "access key ring" + desc = "A key ring with a beeper, allowing the keys to change shape depending on which department it has access to." + icon_state = "access_key" + inhand_icon_state = "access_key" + icon = 'icons/obj/janitor.dmi' + lefthand_file = 'icons/mob/inhands/items/keys_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items/keys_righthand.dmi' + hitsound = 'sound/items/rattling_keys_attack.ogg' + force = 2 + verb_say = "beeps" //it has a beeper + verb_ask = "questionably beeps" + verb_exclaim = "beeps loudly" + w_class = WEIGHT_CLASS_TINY + + ///The departmental access given to the key. + var/list/department_access + +/obj/item/access_key/Initialize(mapload) + . = ..() + RegisterSignal(SSdcs, COMSIG_ON_DEPARTMENT_ACCESS, PROC_REF(department_access_given)) + +/obj/item/access_key/examine(mob/user) + . = ..() + if(department_access) + . += "It currently holds access to the [department_access] region." + +/obj/item/access_key/examine_more(mob/user) + . = ..() + . += span_notice("Access can be granted through a Keycard Authentication Device.") + . += span_notice("This access is limited to one department at a time.") + +/** + * Called when attempting to open an airlock. + * Checks if the keys have access, returns try_to_activate_door if it does, returns FALSE otherwise. + * Args: + * user - The person attempting to open the door + * airlock - The door we're attempting to open + */ +/obj/item/access_key/proc/attempt_open_door(mob/living/user, obj/machinery/door/airlock) + if(DOING_INTERACTION_WITH_TARGET(user, airlock)) + return + user.balloon_alert_to_viewers("fumbles with keys...", "finding key...") + user.playsound_local(src, 'sound/items/rattling_keys.ogg', 25, TRUE) + if(!do_after(user, 3 SECONDS, airlock)) + return FALSE + if(!department_access || !airlock.check_access_list(SSid_access.accesses_by_region[department_access])) + airlock.balloon_alert(user, "no access!") + return FALSE + return airlock.try_to_activate_door(user, access_bypass = TRUE) + +/** + * Called when a keycard authenticator sends region access + * Stores it on the key to use for access, then sets a timer to clear it. + * Args: + * source - the keycard authenticator giving us the access + * region_access - the list of access we're being sent, we only take the first entry in the list as there should only have one department at a time. + */ +/obj/item/access_key/proc/department_access_given(obj/machinery/keycard_auth/source, list/region_access) + SIGNAL_HANDLER + department_access = region_access[1] + say("Access granted to [department_access] area.") + playsound(src, 'sound/machines/ding.ogg', 25, TRUE) + addtimer(CALLBACK(src, PROC_REF(clear_access)), ACCESS_TIMER_LIMIT, TIMER_UNIQUE|TIMER_OVERRIDE) + log_game("Access to the [department_access] department was given to [src] [(ismob(loc)) ? "held by [loc]" : "which is not being held"]") + investigate_log("Access to the [department_access] department was given to [src] [(ismob(loc)) ? "held by [loc]" : "which is not being held"]", INVESTIGATE_ACCESSCHANGES) + +/** + * Called when a keycard authenticator runs out of time + * Clears the department access and alerts nearby people of such. + */ +/obj/item/access_key/proc/clear_access() + log_game("Access to the [department_access] department on [src] has expired.") + investigate_log("Access to the [department_access] department on [src] has expired.]", INVESTIGATE_ACCESSCHANGES) + department_access = null + say("Access revoked, time ran out.") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE) + +#undef ACCESS_TIMER_LIMIT diff --git a/code/modules/jobs/job_types/janitor.dm b/code/modules/jobs/job_types/janitor.dm index d946fe9e93e..a23043c5c7f 100644 --- a/code/modules/jobs/job_types/janitor.dm +++ b/code/modules/jobs/job_types/janitor.dm @@ -32,7 +32,6 @@ job_tone = "slip" - /datum/outfit/job/janitor name = "Janitor" jobtype = /datum/job/janitor @@ -42,12 +41,17 @@ belt = /obj/item/modular_computer/pda/janitor ears = /obj/item/radio/headset/headset_srv -/datum/outfit/job/janitor/pre_equip(mob/living/carbon/human/H, visualsOnly) +/datum/outfit/job/janitor/pre_equip(mob/living/carbon/human/human_equipper, visuals_only) . = ..() if(check_holidays(GARBAGEDAY)) backpack_contents += list(/obj/item/gun/ballistic/revolver) r_pocket = /obj/item/ammo_box/a357 + var/static/access_key_given = FALSE + if(!access_key_given && !visuals_only) + access_key_given = TRUE + backpack_contents += list(/obj/item/access_key) + /datum/outfit/job/janitor/get_types_to_preload() . = ..() if(check_holidays(GARBAGEDAY)) diff --git a/code/modules/security_levels/keycard_authentication.dm b/code/modules/security_levels/keycard_authentication.dm index 377d3a80aca..a7bd8a57f91 100644 --- a/code/modules/security_levels/keycard_authentication.dm +++ b/code/modules/security_levels/keycard_authentication.dm @@ -4,9 +4,11 @@ GLOBAL_DATUM_INIT(keycard_events, /datum/events, new) #define KEYCARD_EMERGENCY_MAINTENANCE_ACCESS "Emergency Maintenance Access" #define KEYCARD_BSA_UNLOCK "Bluespace Artillery Unlock" +#define ACCESS_GRANTING_COOLDOWN (30 SECONDS) + /obj/machinery/keycard_auth name = "Keycard Authentication Device" - desc = "This device is used to trigger station functions, which require more than one ID card to authenticate." + desc = "This device is used to trigger station functions, which require more than one ID card to authenticate, or to give the Janitor access to a department." icon = 'icons/obj/monitors.dmi' icon_state = "auth_off" power_channel = AREA_USAGE_ENVIRON @@ -19,6 +21,8 @@ GLOBAL_DATUM_INIT(keycard_events, /datum/events, new) var/mob/triggerer = null var/waiting = FALSE + COOLDOWN_DECLARE(access_grant_cooldown) + MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/keycard_auth, 26) /obj/machinery/keycard_auth/Initialize(mapload) @@ -61,9 +65,9 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/keycard_auth, 26) /obj/machinery/keycard_auth/ui_act(action, params) . = ..() - if(. || waiting || !allowed(usr)) return + switch(action) if("red_alert") if(!event_source) @@ -83,6 +87,24 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/keycard_auth, 26) if(!event_source) sendEvent(KEYCARD_BSA_UNLOCK) . = TRUE + if("give_janitor_access") + var/mob/living/living_user = usr + if(!living_user || !istype(living_user)) + return TRUE + if(!COOLDOWN_FINISHED(src, access_grant_cooldown)) + balloon_alert(usr, "on cooldown!") + return TRUE + var/obj/item/card/id/advanced/card = living_user.get_idcard(hand_first = TRUE) + if(!card) + return TRUE + for(var/access_as_text in SSid_access.sub_department_managers_tgui) + var/list/info = SSid_access.sub_department_managers_tgui[access_as_text] + if(card.trim.assignment != info["head"]) + continue + COOLDOWN_START(src, access_grant_cooldown, ACCESS_GRANTING_COOLDOWN) + SEND_GLOBAL_SIGNAL(COMSIG_ON_DEPARTMENT_ACCESS, info["regions"]) + balloon_alert(usr, "key access sent") + return /obj/machinery/keycard_auth/update_appearance(updates) . = ..() @@ -163,6 +185,7 @@ GLOBAL_VAR_INIT(emergency_access, FALSE) minor_announce("Bluespace Artillery firing protocols have been [GLOB.bsa_unlock? "unlocked" : "locked"]", "Weapons Systems Update:") SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("bluespace artillery", GLOB.bsa_unlock? "unlocked" : "locked")) +#undef ACCESS_GRANTING_COOLDOWN #undef KEYCARD_RED_ALERT #undef KEYCARD_EMERGENCY_MAINTENANCE_ACCESS #undef KEYCARD_BSA_UNLOCK diff --git a/icons/mob/inhands/items/keys_lefthand.dmi b/icons/mob/inhands/items/keys_lefthand.dmi new file mode 100644 index 00000000000..e0cb51c0036 Binary files /dev/null and b/icons/mob/inhands/items/keys_lefthand.dmi differ diff --git a/icons/mob/inhands/items/keys_righthand.dmi b/icons/mob/inhands/items/keys_righthand.dmi new file mode 100644 index 00000000000..3254bfc344d Binary files /dev/null and b/icons/mob/inhands/items/keys_righthand.dmi differ diff --git a/icons/obj/janitor.dmi b/icons/obj/janitor.dmi index 7670de40dc4..6e007dc4fa5 100644 Binary files a/icons/obj/janitor.dmi and b/icons/obj/janitor.dmi differ diff --git a/sound/attributions.txt b/sound/attributions.txt index fde05e90b53..6a0a31587a5 100644 --- a/sound/attributions.txt +++ b/sound/attributions.txt @@ -38,6 +38,9 @@ alien_organ_cut.ogg is adapted from some CC 0 flesh sound, lesaucisson's swoosh standard_stamp.ogg is adapted from tom_woysky's "Stamp.wav" https://freesound.org/people/tom_woysky/sounds/348316/ + +rattling_keys.ogg and rattling_keys_attack.ogg is adapted from Desibloke's "Keys rattling [Key rattle]" https://freesound.org/people/Desibloke/sounds/523671/ + chainsaw_loop.ogg is adapted from Audionautics "Chainsaw Idle Loops", which is licensed under CC Attribution 3.0 https://freesound.org/people/Audionautics/sounds/171652/ chainsaw_start.ogg is adapted from 8 Chainsaw Pulls.wav by Stefan021 @@ -45,4 +48,3 @@ https://freesound.org/s/431740/ (CC 0) chainsaw_stop.ogg is adapted from kyles "chainsaw sawing short cuts +stop.flac" (CC 0) https://freesound.org/people/kyles/sounds/453256/ - diff --git a/sound/items/rattling_keys.ogg b/sound/items/rattling_keys.ogg new file mode 100644 index 00000000000..06088b3a756 Binary files /dev/null and b/sound/items/rattling_keys.ogg differ diff --git a/sound/items/rattling_keys_attack.ogg b/sound/items/rattling_keys_attack.ogg new file mode 100644 index 00000000000..65695d650bc Binary files /dev/null and b/sound/items/rattling_keys_attack.ogg differ diff --git a/tgstation.dme b/tgstation.dme index d1a0f7f1595..49683074977 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -268,6 +268,7 @@ #include "code\__DEFINES\dcs\signals\signals_hud.dm" #include "code\__DEFINES\dcs\signals\signals_hydroponic.dm" #include "code\__DEFINES\dcs\signals\signals_janitor.dm" +#include "code\__DEFINES\dcs\signals\signals_key.dm" #include "code\__DEFINES\dcs\signals\signals_ladder.dm" #include "code\__DEFINES\dcs\signals\signals_lift.dm" #include "code\__DEFINES\dcs\signals\signals_light_eater.dm" @@ -1791,6 +1792,7 @@ #include "code\game\objects\items\hourglass.dm" #include "code\game\objects\items\inducer.dm" #include "code\game\objects\items\inspector.dm" +#include "code\game\objects\items\janitor_key.dm" #include "code\game\objects\items\kirbyplants.dm" #include "code\game\objects\items\kitchen.dm" #include "code\game\objects\items\knives.dm" diff --git a/tgui/packages/tgui/interfaces/KeycardAuth.js b/tgui/packages/tgui/interfaces/KeycardAuth.js index 8b803161bef..f38e8b3b680 100644 --- a/tgui/packages/tgui/interfaces/KeycardAuth.js +++ b/tgui/packages/tgui/interfaces/KeycardAuth.js @@ -5,7 +5,7 @@ import { Window } from '../layouts'; export const KeycardAuth = (props, context) => { const { act, data } = useBackend(context); return ( - +
@@ -49,6 +49,12 @@ export const KeycardAuth = (props, context) => { onClick={() => act('bsa_unlock')} content="Bluespace Artillery Unlock" /> +