diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index d5ea8534b3b3..bcbb4896a039 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -69,6 +69,7 @@ GLOBAL_LIST_INIT(channel_tokens, list( if(ispath(keyslot2)) keyslot2 = new keyslot2() set_listening(TRUE) + set_broadcasting(TRUE) recalculateChannels() possibly_deactivate_in_loc() @@ -114,6 +115,22 @@ GLOBAL_LIST_INIT(channel_tokens, list( for(var/language in language_list) user.remove_language(language, understood = TRUE, spoken = FALSE, source = LANGUAGE_RADIOKEY) +// Headsets do not become hearing sensitive as broadcasting instead controls their talk_into capabilities +/obj/item/radio/headset/set_broadcasting(new_broadcasting, actual_setting = TRUE) + broadcasting = new_broadcasting + if(actual_setting) + should_be_broadcasting = broadcasting + + if (perform_update_icon && !isnull(overlay_mic_idle)) + update_icon() + else if (!perform_update_icon) + should_update_icon = TRUE + +/obj/item/radio/headset/talk_into_impl(atom/movable/talking_movable, message, channel, list/spans, datum/language/language, list/message_mods) + if (!broadcasting) + return + return ..() + /obj/item/radio/headset/syndicate //disguised to look like a normal headset for stealth ops /obj/item/radio/headset/syndicate/Initialize(mapload) diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm index 516629dc0929..e48ca6f964e3 100644 --- a/code/game/objects/items/devices/traitordevices.dm +++ b/code/game/objects/items/devices/traitordevices.dm @@ -283,27 +283,70 @@ effective or pretty fucking useless. /obj/item/jammer name = "radio jammer" - desc = "Device used to disrupt nearby radio communication." + desc = "Device used to disrupt nearby radio communication. Alternate function creates a powerful distruptor wave which disables all nearby listening devices." icon = 'icons/obj/device.dmi' icon_state = "jammer" var/active = FALSE var/range = 12 + var/jam_cooldown_duration = 15 SECONDS + COOLDOWN_DECLARE(jam_cooldown) -/obj/item/jammer/attack_self(mob/user) - to_chat(user,span_notice("You [active ? "deactivate" : "activate"] [src].")) +/obj/item/jammer/Initialize(mapload) + . = ..() + register_context() + +/obj/item/jammer/add_context(atom/source, list/context, obj/item/held_item, mob/user) + context[SCREENTIP_CONTEXT_LMB] = "Release distruptor wave" + context[SCREENTIP_CONTEXT_RMB] = "Toggle" + return CONTEXTUAL_SCREENTIP_SET + +/obj/item/jammer/attack_self(mob/user, modifiers) + . = ..() + if (!COOLDOWN_FINISHED(src, jam_cooldown)) + user.balloon_alert(user, "on cooldown!") + return ..() + + user.balloon_alert(user, "distruptor wave released!") + to_chat(user, span_notice("You release a distruptor wave, disabling all nearby radio devices.")) + for (var/atom/potential_owner in view(7, user)) + disable_radios_on(potential_owner) + COOLDOWN_START(src, jam_cooldown, jam_cooldown_duration) + +/obj/item/jammer/attack_self_secondary(mob/user, modifiers) + . = ..() + to_chat(user, span_notice("You [active ? "deactivate" : "activate"] [src].")) + user.balloon_alert(user, "[active ? "deactivated" : "activated"] the jammer") active = !active if(active) GLOB.active_jammers |= src - else GLOB.active_jammers -= src - update_appearance() /obj/item/jammer/Destroy() GLOB.active_jammers -= src return ..() +/obj/item/jammer/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + if(.) + return + + if (!(target in view(7, user))) + user.balloon_alert(user, "out of reach!") + return + + target.balloon_alert(user, "radio distrupted!") + to_chat(user, span_notice("You release a directed distruptor wave, disabling all radio devices on [target].")) + disable_radios_on(target) + +/obj/item/jammer/proc/disable_radios_on(atom/target) + for (var/obj/item/radio/radio in target.get_all_contents() + target) + radio.set_broadcasting(FALSE) + // MONKESTATION EDIT: Radio jammers turn body cameras off too. + for(var/obj/item/bodycam_upgrade/bodycamera in target.get_all_contents() + target) + bodycamera.turn_off() + /obj/item/storage/toolbox/emergency/turret desc = "You feel a strange urge to hit this with a wrench." diff --git a/monkestation/code/modules/clothing/suit_accessories/bodycamera.dm b/monkestation/code/modules/clothing/suit_accessories/bodycamera.dm index d6ce896e499c..8a2b31d24cf4 100644 --- a/monkestation/code/modules/clothing/suit_accessories/bodycamera.dm +++ b/monkestation/code/modules/clothing/suit_accessories/bodycamera.dm @@ -1,156 +1,174 @@ - -/obj/item/clothing/suit/armor/Initialize(mapload) - . = ..() - AddComponent(/datum/component/bodycamera_holder) - -/obj/item/clothing/suit/hooded/wintercoat/security/Initialize(mapload) - . = ..() - AddComponent(/datum/component/bodycamera_holder) - -/* - The bodycamera - This is the item that gets installed into items that have the bodycamera_holder element -*/ +/** + * Bodycamera Upgrade + * + * An item that can be inserted into any piece of clothing that goes on the Exosuit slot. + * Allows you to turn it on/off with an ID card to add the suit & the wearer's name into the security camera system. + */ /obj/item/bodycam_upgrade name = "\improper body camera" - icon = 'monkestation/code/modules/clothing/suit_accessories/bodycamera.dmi' + desc = "A body camera device attachable to most outerwear. There's an instructions tag if you look a little closer..." + icon = 'monkestation/icons/obj/clothing/bodycamera.dmi' icon_state = "bodycamera" - desc = "An armor vest upgrade, there's an instructions tag if you look a little closer..." - w_class = WEIGHT_CLASS_SMALL - ///The camera itself. - var/obj/machinery/camera/builtin_bodycamera + + ///The network we give to the builtin body camera while it's on and active. + var/list/network = list("ss13") + ///The camera itself, made when we need it and deleted on Destroy. Installed into the clothing item directly. + var/obj/machinery/camera/bodycamera/builtin_bodycamera + /** + * Sprites by: @Partheo from Yogstation, colors very, very slightly edited. + * A static overlay put onto any clothing item that has the camera installed. + */ + var/static/mutable_appearance/equipped_overlay = mutable_appearance('monkestation/icons/mob/clothing/bodycamera_overlay.dmi', "bodycamera") /obj/item/bodycam_upgrade/examine_more(mob/user) . = ..() - . += list(span_notice("Use [src] on any valid vest to quickly install.")) - . += list(span_notice("Use a [span_bold("screwdriver")] to remove it.")) - . += list(span_notice("While equipped, use your ID card on the vest to activate/deactivate the camera.")) - . += list(span_notice("Unequipping the vest will immediately deactivate the camera.")) - -/obj/item/bodycam_upgrade/Destroy() - if(builtin_bodycamera) - turn_off() + . += list(span_notice("You can use [name] on any outerwear to install it, automatically turning on if the outerwear is equipped.")) + . += list(span_notice("Once installed, you can use an [EXAMINE_HINT("ID card")] to turn the camera on and off.")) + +/obj/item/bodycam_upgrade/Destroy(force) + if(!isnull(builtin_bodycamera)) + QDEL_NULL(builtin_bodycamera) return ..() -/obj/item/bodycam_upgrade/proc/is_on() - if(isnull(builtin_bodycamera)) - return FALSE - return TRUE - -/obj/item/bodycam_upgrade/proc/turn_on(mob/user, obj/item/card/id/id_card) - builtin_bodycamera = new(user) - builtin_bodycamera.internal_light = FALSE - builtin_bodycamera.network = list("ss13") - builtin_bodycamera.c_tag = "-Body Camera: [(id_card.registered_name)]" //([id_card.assignment])" //remove assignment because that's just too much text. - playsound(loc, 'sound/machines/twobeep.ogg', get_clamped_volume(), TRUE, -1) +/obj/item/bodycam_upgrade/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + if(.) + return + if(!proximity_flag || !isitem(target)) + return + var/obj/item/interacting_item = target + if(!(interacting_item.slot_flags & ITEM_SLOT_OCLOTHING)) + return + if(interacting_item.item_flags & (ABSTRACT|DROPDEL)) //things like changeling suits don't get body cameras. + return + install_camera(interacting_item, user) + +///Installs the bodycamera into a piece of clothing, updating the overlays on the mob if they're actively wearing it. +/obj/item/bodycam_upgrade/proc/install_camera(obj/item/installing_into, mob/user) + var/obj/item/bodycam_upgrade/existing_upgrade = locate() in installing_into.contents + if(existing_upgrade) + //this is where your mouse is, so more likely where you're looking. + installing_into.balloon_alert(user, "camera already installed!") + playsound(installing_into, 'sound/machines/buzz-two.ogg', 20, TRUE, -1) + return + installing_into.add_overlay(equipped_overlay) + forceMove(installing_into) + playsound(installing_into, 'sound/items/drill_use.ogg', 20, TRUE, -1) + RegisterSignal(installing_into, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby)) + RegisterSignal(installing_into, COMSIG_ATOM_EXAMINE_MORE, PROC_REF(on_examine_more)) + RegisterSignal(installing_into, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), PROC_REF(on_screwdriver_act)) + RegisterSignal(installing_into, COMSIG_ITEM_GET_WORN_OVERLAYS, PROC_REF(on_checked_overlays)) + RegisterSignal(installing_into, COMSIG_ATOM_EMP_ACT, PROC_REF(on_emp_act)) + if(user.get_item_by_slot(ITEM_SLOT_OCLOTHING) == installing_into) + user.update_worn_oversuit() + turn_on(user) + +///Uninstalls the bodycamera from a piece of clothing. +/obj/item/bodycam_upgrade/proc/uninstall_camera(obj/item/taking_from, mob/user) + UnregisterSignal(taking_from, list( + COMSIG_ATOM_ATTACKBY, + COMSIG_ATOM_EXAMINE_MORE, + COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), + COMSIG_ITEM_GET_WORN_OVERLAYS, + COMSIG_ATOM_EMP_ACT + )) + if(builtin_bodycamera) //retract the camera back in. + builtin_bodycamera.forceMove(src) + builtin_bodycamera.network = list() + taking_from.cut_overlay(equipped_overlay) + forceMove(user.drop_location()) + turn_off() + user.put_in_hands(src) + if(user.get_item_by_slot(ITEM_SLOT_OCLOTHING) == taking_from) + user.update_worn_oversuit() + +///Turns the camera on. Will be silent if 'user' is null, but it REQUIRES either a user or a provided ID. +///Because cameras are named after the ID, or person if there isn't one, then having neither means we can't turn +///on at all. +/obj/item/bodycam_upgrade/proc/turn_on(mob/living/user, obj/item/card/id/id_card) + if(!id_card && !user) + return + if(!builtin_bodycamera) + builtin_bodycamera = new(loc) //made in the vest it's located in. + if(!id_card) + id_card = user.get_idcard() || null + if(id_card) + builtin_bodycamera.c_tag = "-Body Camera: [(id_card.registered_name)] ([id_card.assignment])" + else + builtin_bodycamera.c_tag = "-Body Camera: [(user.name)]" if(user) - user.balloon_alert(user, "bodycamera activated.") + user.balloon_alert(user, "bodycamera activated") + playsound(loc, 'sound/machines/beep.ogg', get_clamped_volume(), TRUE, -1) + builtin_bodycamera.network = network //sync the network of the camera to us, the upgrade. + builtin_bodycamera.status = TRUE +///Turns the camera off. Will be silent if 'user' is null. /obj/item/bodycam_upgrade/proc/turn_off(mob/user) if(user) - user.balloon_alert(user, "bodycamera deactivated.") - playsound(loc, 'sound/machines/triple_beep.ogg', get_clamped_volume(), TRUE, -1) - QDEL_NULL(builtin_bodycamera) - -//Body Camera box -/obj/item/storage/box/bodycamera - name = "box of bodycameras" - desc = "A box full of bodycameras." - icon_state = "secbox" - -/obj/item/storage/box/bodycamera/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/bodycam_upgrade(src) + user.balloon_alert(user, "bodycamera deactivated") + playsound(loc, 'sound/machines/beep.ogg', get_clamped_volume(), TRUE, -1) + builtin_bodycamera.status = FALSE /** - * Bodycamera component + * on_emp_act * - * Allows anything to have a body camera inserted into it + * Called when the body camera is EMPed. + * Will destroy the camera, regardless of whether it is equipped or not. */ - -/datum/component/bodycamera_holder - ///The installed bodycamera - var/obj/item/bodycam_upgrade/bodycamera_installed - ///The clothing part this needs to be on. This could possibly just be done by checking the clothing's slot - var/clothingtype_required = ITEM_SLOT_OCLOTHING - -/datum/component/bodycamera_holder/RegisterWithParent() - . = ..() - RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby)) - RegisterSignal(parent, COMSIG_ATOM_EXAMINE_MORE, PROC_REF(on_examine_more)) - RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), PROC_REF(on_screwdriver_act)) - -/datum/component/bodycamera_holder/UnregisterFromParent() - UnregisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER)) - UnregisterSignal(parent, COMSIG_ATOM_ATTACKBY) - UnregisterSignal(parent, COMSIG_ATOM_EXAMINE) - QDEL_NULL(bodycamera_installed) - return ..() - -/datum/component/bodycamera_holder/proc/turn_camera_on(mob/living/user, obj/item/card) - RegisterSignal(parent, COMSIG_ITEM_POST_UNEQUIP, PROC_REF(on_unequip)) - bodycamera_installed.turn_on(user, card) - -/datum/component/bodycamera_holder/proc/turn_camera_off(mob/living/user) - UnregisterSignal(parent, COMSIG_ITEM_POST_UNEQUIP) - bodycamera_installed.turn_off(user) - -/// When the camera holder is unequipped -/datum/component/bodycamera_holder/proc/on_unequip(mob/living/source, force, atom/newloc, no_move, invdrop, silent) +/obj/item/bodycam_upgrade/proc/on_emp_act(atom/source, severity, protection) SIGNAL_HANDLER - turn_camera_off() + if(protection & EMP_PROTECT_SELF) + return + if(!isnull(builtin_bodycamera)) + QDEL_NULL(builtin_bodycamera) -/// When examining -/datum/component/bodycamera_holder/proc/on_examine_more(atom/source, mob/user, list/examine_list) +/** + * Examine more + * + * Called when the item the bodycamera is installed into is double-examined, + * letting it know it's examined and how to get it out. + */ +/obj/item/bodycam_upgrade/proc/on_examine_more(atom/source, mob/user, list/examine_list) SIGNAL_HANDLER + examine_list += span_notice("It has [name] installed. You can toggle it with an [EXAMINE_HINT("ID card")] or remove it with a [EXAMINE_HINT("screwdriver")].") - if(bodycamera_installed) - examine_list += span_notice("It has [bodycamera_installed] installed.") - else - examine_list += span_notice("It has a spot to hook up a body camera onto.") - -/// When items are used on it. Bodycamera/ID card -/datum/component/bodycamera_holder/proc/on_attackby(datum/source, obj/item/item, mob/living/user) +/** + * Screwdriver act + * + * Called when a screwdriver is used on the item the bodycamera is installed into. + * Removes the bodycamera from the clothing and puts it in the user's hand. + */ +/obj/item/bodycam_upgrade/proc/on_screwdriver_act(atom/source, mob/user, obj/item/tool) SIGNAL_HANDLER - if(istype(item, /obj/item/bodycam_upgrade)) - if(bodycamera_installed) - to_chat(user, span_warning("We have already installed [bodycamera_installed] installed!")) - playsound(source, 'sound/machines/buzz-two.ogg', item.get_clamped_volume(), TRUE, -1) - else - item.forceMove(source) - bodycamera_installed = item - to_chat(user, span_warning("You install [item] into [source].")) - playsound(source, 'sound/items/drill_use.ogg', item.get_clamped_volume(), TRUE, -1) - return + playsound(source, 'sound/items/drill_use.ogg', tool.get_clamped_volume(), TRUE, -1) + INVOKE_ASYNC(src, PROC_REF(uninstall_camera), source, user) - if(!bodycamera_installed) - return +/** + * On Attackby + * + * Called when the piece of clothing the bodycamera is installed into is attacked by an item. + * If the item is an ID card, it will turn the camera on/off. + */ +/obj/item/bodycam_upgrade/proc/on_attackby(datum/source, obj/item/item, mob/living/user) + SIGNAL_HANDLER var/obj/item/card/id/card = item.GetID() if(!card) return - var/obj/item/clothing/suit_slot = user.get_item_by_slot(clothingtype_required) - if(!istype(suit_slot)) - to_chat(user, span_warning("You have to be wearing [source] to turn [bodycamera_installed] on!")) - return - //Do we have a camera on or off? - if(bodycamera_installed.is_on()) - turn_camera_off(user) - else - turn_camera_on(user, card) + if(builtin_bodycamera?.status) + return turn_off(user) + return turn_on(user, card) -/// When a screwdriver is used on it -/datum/component/bodycamera_holder/proc/on_screwdriver_act(atom/source, mob/user, obj/item/tool) +/** + * On checked overlays + * + * Called when the item the bodycamera is installed into is getting their worn overlays updated. + * We add our body camera overlay to the list of overlays. + */ +/obj/item/bodycam_upgrade/proc/on_checked_overlays(obj/item/source, list/overlays, mutable_appearance/standing, isinhands, icon_file) SIGNAL_HANDLER - - if(!bodycamera_installed) + if(isinhands) return - if(bodycamera_installed.is_on()) - turn_camera_off(user) - to_chat(user, span_warning("You remove the [bodycamera_installed] from [source].")) - playsound(source, 'sound/items/drill_use.ogg', tool.get_clamped_volume(), TRUE, -1) - bodycamera_installed.forceMove(user.loc) - INVOKE_ASYNC(user, TYPE_PROC_REF(/mob, put_in_hands), bodycamera_installed) - bodycamera_installed = null + overlays += equipped_overlay diff --git a/monkestation/code/modules/clothing/suit_accessories/bodycamera_box.dm b/monkestation/code/modules/clothing/suit_accessories/bodycamera_box.dm new file mode 100644 index 000000000000..2d6718befbd8 --- /dev/null +++ b/monkestation/code/modules/clothing/suit_accessories/bodycamera_box.dm @@ -0,0 +1,8 @@ +/obj/item/storage/box/bodycamera + name = "box of bodycameras" + desc = "A box full of bodycameras." + icon_state = "secbox" + +/obj/item/storage/box/bodycamera/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/bodycam_upgrade(src) diff --git a/monkestation/code/modules/clothing/suit_accessories/camera.dm b/monkestation/code/modules/clothing/suit_accessories/camera.dm new file mode 100644 index 000000000000..dfa485257666 --- /dev/null +++ b/monkestation/code/modules/clothing/suit_accessories/camera.dm @@ -0,0 +1,8 @@ +/** + * Bodycamera subtype of Camera + * Meant to make sure AIs and such cannot use this as pure vision, + * and can't be 'disabled' by roundstart, though that really shouldn't happen anyway. + */ +/obj/machinery/camera/bodycamera + start_active = TRUE + internal_light = FALSE diff --git a/monkestation/icons/mob/clothing/bodycamera_overlay.dmi b/monkestation/icons/mob/clothing/bodycamera_overlay.dmi new file mode 100644 index 000000000000..be8ad8c17d25 Binary files /dev/null and b/monkestation/icons/mob/clothing/bodycamera_overlay.dmi differ diff --git a/monkestation/code/modules/clothing/suit_accessories/bodycamera.dmi b/monkestation/icons/obj/clothing/bodycamera.dmi similarity index 100% rename from monkestation/code/modules/clothing/suit_accessories/bodycamera.dmi rename to monkestation/icons/obj/clothing/bodycamera.dmi diff --git a/tgstation.dme b/tgstation.dme index 830016c00216..3b6b8fc9bcae 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -7124,6 +7124,8 @@ #include "monkestation\code\modules\clothing\spacesuits\hardsuits\science.dm" #include "monkestation\code\modules\clothing\spacesuits\hardsuits\security.dm" #include "monkestation\code\modules\clothing\suit_accessories\bodycamera.dm" +#include "monkestation\code\modules\clothing\suit_accessories\bodycamera_box.dm" +#include "monkestation\code\modules\clothing\suit_accessories\camera.dm" #include "monkestation\code\modules\clothing\suits\coats.dm" #include "monkestation\code\modules\clothing\suits\costume.dm" #include "monkestation\code\modules\clothing\suits\toggles.dm"