diff --git a/code/__DEFINES/dcs/signals/atom/signals_item.dm b/code/__DEFINES/dcs/signals/atom/signals_item.dm index f038d4c5dbce..5ba79960657b 100644 --- a/code/__DEFINES/dcs/signals/atom/signals_item.dm +++ b/code/__DEFINES/dcs/signals/atom/signals_item.dm @@ -29,8 +29,10 @@ #define COMSIG_ITEM_PICKUP "item_pickup" -///from /obj/item/device/camera/broadcasting/attack_self +///from /obj/item/device/camera/broadcasting #define COMSIG_BROADCAST_GO_LIVE "broadcast_live" +#define COMSIG_BROADCAST_HEAR_TALK "broadcast_hear_talk" +#define COMSIG_BROADCAST_SEE_EMOTE "broadcast_see_emote" /// from /obj/item/proc/mob_can_equip #define COMSIG_ITEM_ATTEMPTING_EQUIP "item_attempting_equip" diff --git a/code/__DEFINES/equipment.dm b/code/__DEFINES/equipment.dm index 4368bf304a48..b37ae0d59d64 100644 --- a/code/__DEFINES/equipment.dm +++ b/code/__DEFINES/equipment.dm @@ -44,6 +44,8 @@ #define USES_HEARING (1<<17) /// Should we use the initial icon for display? Mostly used by overlay only objects #define HTML_USE_INITAL_ICON (1<<18) +// Whether or not the object sees emotes +#define USES_SEEING (1<<19) //========================================================================================== diff --git a/code/datums/emotes.dm b/code/datums/emotes.dm index b691d87a2169..6e84052720d4 100644 --- a/code/datums/emotes.dm +++ b/code/datums/emotes.dm @@ -112,6 +112,7 @@ var/paygrade = user.get_paygrade() var/formatted_message = "[paygrade][user] [msg]" var/user_turf = get_turf(user) + var/list/seeing_obj = list() if (user.client) for(var/mob/ghost as anything in GLOB.dead_mob_list) if(!ghost.client || isnewplayer(ghost)) @@ -132,12 +133,18 @@ if(emote_type & EMOTE_VISIBLE) var/list/viewers = get_mobs_in_view(7, user) for(var/mob/current_mob in viewers) + for(var/obj/object in current_mob.contents) + if((object.flags_atom & USES_SEEING)) + seeing_obj |= object if(!(current_mob.client?.prefs.toggles_langchat & LANGCHAT_SEE_EMOTES)) viewers -= current_mob run_langchat(user, viewers) else if(emote_type & EMOTE_AUDIBLE) var/list/heard = get_mobs_in_view(7, user) for(var/mob/current_mob in heard) + for(var/obj/object in current_mob.contents) + if((object.flags_atom & USES_HEARING)) + seeing_obj |= object if(current_mob.ear_deaf) heard -= current_mob continue @@ -145,6 +152,9 @@ heard -= current_mob run_langchat(user, heard) + for(var/obj/object as anything in seeing_obj) + object.see_emote(user, msg, (emote_type & EMOTE_AUDIBLE)) + SEND_SIGNAL(user, COMSIG_MOB_EMOTED(key)) diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 948d83e76148..6943544e30d4 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -312,8 +312,8 @@ GLOBAL_LIST_EMPTY_TYPED(all_cameras, /obj/structure/machinery/camera) . = ..() if(!camera_item) return INITIALIZE_HINT_QDEL - c_tag = camera_item.get_broadcast_name() linked_broadcasting = camera_item + c_tag = linked_broadcasting.get_broadcast_name() /obj/structure/machinery/camera/mortar alpha = 0 diff --git a/code/game/machinery/computer/camera_console.dm b/code/game/machinery/computer/camera_console.dm index cd0ee780f478..1e2cb427cab4 100644 --- a/code/game/machinery/computer/camera_console.dm +++ b/code/game/machinery/computer/camera_console.dm @@ -17,6 +17,7 @@ var/colony_camera_mapload = TRUE var/admin_console = FALSE + var/stay_connected = FALSE /obj/structure/machinery/computer/cameras/Initialize(mapload) . = ..() @@ -33,7 +34,7 @@ /obj/structure/machinery/computer/cameras/Destroy() SStgui.close_uis(src) - QDEL_NULL(current) + current = null UnregisterSignal(src, COMSIG_CAMERA_MAPNAME_ASSIGNED) last_camera_turf = null concurrent_users = null @@ -147,7 +148,7 @@ // Unregister map objects SEND_SIGNAL(src, COMSIG_CAMERA_UNREGISTER_UI, user) // Turn off the console - if(length(concurrent_users) == 0 && is_living) + if(length(concurrent_users) == 0 && is_living && !stay_connected) current = null SEND_SIGNAL(src, COMSIG_CAMERA_CLEAR) last_camera_turf = null @@ -206,6 +207,8 @@ name = "Television Set" desc = "An old TV hooked up to a video cassette recorder, you can even use it to time shift WOW." network = list(CAMERA_NET_CORRESPONDENT) + stay_connected = TRUE + circuit = /obj/item/circuitboard/computer/cameras/tv var/obj/item/device/camera/broadcasting/broadcastingcamera = null /obj/structure/machinery/computer/cameras/wooden_tv/broadcast/Destroy() @@ -213,38 +216,76 @@ return ..() /obj/structure/machinery/computer/cameras/wooden_tv/broadcast/ui_state(mob/user) - return GLOB.default_state + return GLOB.in_view /obj/structure/machinery/computer/cameras/wooden_tv/broadcast/ui_act(action, params) . = ..() if(action != "switch_camera") return - broadcastingcamera = null - if (!istype(current, /obj/structure/machinery/camera/correspondent)) + if(broadcastingcamera) + clear_camera() + if(!istype(current, /obj/structure/machinery/camera/correspondent)) return var/obj/structure/machinery/camera/correspondent/corr_cam = current - if (!corr_cam.linked_broadcasting) + if(!corr_cam.linked_broadcasting) return broadcastingcamera = corr_cam.linked_broadcasting RegisterSignal(broadcastingcamera, COMSIG_BROADCAST_GO_LIVE, PROC_REF(go_back_live)) + RegisterSignal(broadcastingcamera, COMSIG_COMPONENT_ADDED, PROC_REF(handle_rename)) RegisterSignal(broadcastingcamera, COMSIG_PARENT_QDELETING, PROC_REF(clear_camera)) + RegisterSignal(broadcastingcamera, COMSIG_BROADCAST_HEAR_TALK, PROC_REF(transfer_talk)) + RegisterSignal(broadcastingcamera, COMSIG_BROADCAST_SEE_EMOTE, PROC_REF(transfer_emote)) /obj/structure/machinery/computer/cameras/wooden_tv/broadcast/ui_close(mob/user) . = ..() - if (!current && broadcastingcamera) + if(!broadcastingcamera) + return + if(!current) clear_camera() /obj/structure/machinery/computer/cameras/wooden_tv/broadcast/proc/clear_camera() SIGNAL_HANDLER - UnregisterSignal(broadcastingcamera, list(COMSIG_BROADCAST_GO_LIVE, COMSIG_PARENT_QDELETING)) + UnregisterSignal(broadcastingcamera, list(COMSIG_BROADCAST_GO_LIVE, COMSIG_PARENT_QDELETING, COMSIG_COMPONENT_ADDED, COMSIG_BROADCAST_HEAR_TALK, COMSIG_BROADCAST_SEE_EMOTE)) broadcastingcamera = null /obj/structure/machinery/computer/cameras/wooden_tv/broadcast/proc/go_back_live(obj/item/device/camera/broadcasting/broadcastingcamera) SIGNAL_HANDLER - if (current.c_tag == broadcastingcamera.get_broadcast_name()) + if(current.c_tag == broadcastingcamera.get_broadcast_name()) current = broadcastingcamera.linked_cam SEND_SIGNAL(src, COMSIG_CAMERA_SET_TARGET, broadcastingcamera.linked_cam, broadcastingcamera.linked_cam.view_range, broadcastingcamera.linked_cam.view_range) +/obj/structure/machinery/computer/cameras/wooden_tv/broadcast/proc/transfer_talk(obj/item/camera, mob/living/sourcemob, message, verb = "says", datum/language/language, italics = FALSE, show_message_above_tv = FALSE) + SIGNAL_HANDLER + if(inoperable()) + return + if(show_message_above_tv) + langchat_speech(message, get_mobs_in_view(7, src), language, sourcemob.langchat_color, FALSE, LANGCHAT_FAST_POP, list(sourcemob.langchat_styles)) + for(var/datum/weakref/user_ref in concurrent_users) + var/mob/user = user_ref.resolve() + if(user?.client?.prefs && !user.client.prefs.lang_chat_disabled && !user.ear_deaf && user.say_understands(sourcemob, language)) + sourcemob.langchat_display_image(user) + +/obj/structure/machinery/computer/cameras/wooden_tv/broadcast/proc/transfer_emote(obj/item/camera, mob/living/sourcemob, emote, audible = FALSE, show_message_above_tv = FALSE) + SIGNAL_HANDLER + if(inoperable()) + return + if(show_message_above_tv) + langchat_speech(emote, get_mobs_in_view(7, src), null, null, TRUE, LANGCHAT_FAST_POP, list("emote")) + for(var/datum/weakref/user_ref in concurrent_users) + var/mob/user = user_ref.resolve() + if(user?.client?.prefs && (user.client.prefs.toggles_langchat & LANGCHAT_SEE_EMOTES) && (!audible || !user.ear_deaf)) + sourcemob.langchat_display_image(user) + +/obj/structure/machinery/computer/cameras/wooden_tv/broadcast/examine(mob/user) + . = ..() + attack_hand(user) //watch tv on examine + +/obj/structure/machinery/computer/cameras/wooden_tv/broadcast/proc/handle_rename(obj/item/camera, datum/component/label) + SIGNAL_HANDLER + if(!istype(label, /datum/component/label)) + return + current.c_tag = broadcastingcamera.get_broadcast_name() + /obj/structure/machinery/computer/cameras/wooden_tv/ot name = "Mortar Monitoring Set" desc = "A Console linked to Mortar launched cameras." diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index a3febb11dddb..66b41fff9127 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -208,6 +208,9 @@ /obj/proc/hear_talk(mob/living/M as mob, msg, verb="says", datum/language/speaking, italics = 0) return +/obj/proc/see_emote(mob/living/M as mob, emote, audible = FALSE) + return + /obj/attack_hand(mob/user) if(can_buckle) manual_unbuckle(user) else . = ..() diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm index 5614a4ffe52b..df39248e343a 100644 --- a/code/modules/paperwork/photography.dm +++ b/code/modules/paperwork/photography.dm @@ -359,6 +359,12 @@ flags_equip_slot = NO_FLAGS //cannot be equiped var/obj/structure/machinery/camera/correspondent/linked_cam +/obj/item/device/camera/broadcasting/Initialize(mapload, ...) + . = ..() + linked_cam = new(loc, src) + linked_cam.status = FALSE + RegisterSignal(src, COMSIG_COMPONENT_ADDED, PROC_REF(handle_rename)) + /obj/item/device/camera/broadcasting/Destroy() clear_broadcast() return ..() @@ -367,13 +373,25 @@ . = ..() if(!.) return - linked_cam = new(loc, src) + flags_atom |= (USES_HEARING|USES_SEEING) + if(!linked_cam || QDELETED(linked_cam)) + linked_cam = new(loc, src) + else + linked_cam.status = TRUE + linked_cam.forceMove(loc) SEND_SIGNAL(src, COMSIG_BROADCAST_GO_LIVE) to_chat(user, SPAN_NOTICE("[src] begins to buzz softly as you go live.")) /obj/item/device/camera/broadcasting/unwield(mob/user) . = ..() - clear_broadcast() + flags_atom &= ~(USES_HEARING|USES_SEEING) + linked_cam.status = FALSE + +/obj/item/device/camera/broadcasting/proc/handle_rename(obj/item/camera, datum/component/label) + SIGNAL_HANDLER + if(!istype(label, /datum/component/label)) + return + linked_cam.c_tag = get_broadcast_name() /obj/item/device/camera/broadcasting/proc/clear_broadcast() if(!QDELETED(linked_cam)) @@ -385,6 +403,12 @@ return src_label_component.label_name return "Broadcast [serial_number]" +/obj/item/device/camera/broadcasting/hear_talk(mob/living/sourcemob, message, verb = "says", datum/language/language, italics = FALSE) + SEND_SIGNAL(src, COMSIG_BROADCAST_HEAR_TALK, sourcemob, message, verb, language, italics, get_dist(sourcemob, src) < 3) + +/obj/item/device/camera/broadcasting/see_emote(mob/living/sourcemob, emote, audible = FALSE) + SEND_SIGNAL(src, COMSIG_BROADCAST_SEE_EMOTE, sourcemob, emote, audible, get_dist(sourcemob, src) < 3 && audible) + /obj/item/photo/proc/construct(datum/picture/P) icon = P.fields["icon"] tiny = P.fields["tiny"] diff --git a/code/modules/tgui/states.dm b/code/modules/tgui/states.dm index 0ec570ca9244..d826840dc495 100644 --- a/code/modules/tgui/states.dm +++ b/code/modules/tgui/states.dm @@ -105,3 +105,26 @@ return UI_DISABLED // Otherwise, we got nothing. return UI_CLOSE + +/** + * public + * + * Check if in view. Can interact only if adjacent, updates within max distance, otherwise closes + * + * required src_object atom/movable The object which owns the UI. + * + * return UI_state The state of the UI. + */ +/mob/living/proc/shared_living_ui_in_view(atom/movable/src_object, viewcheck = TRUE, max_distance = 7) + // If the object is obscured, close it. + if(viewcheck && !(src_object in view(src))) + return UI_CLOSE + var/dist = get_dist(src_object, src) + // Open and interact if 1-0 tiles away. + if(dist <= 1) + return UI_INTERACTIVE + // View only if within distance. + else if(dist <= max_distance) + return UI_UPDATE + // Otherwise, we got nothing. + return UI_CLOSE diff --git a/code/modules/tgui/states/in_view.dm b/code/modules/tgui/states/in_view.dm new file mode 100644 index 000000000000..76ab16e8c794 --- /dev/null +++ b/code/modules/tgui/states/in_view.dm @@ -0,0 +1,15 @@ +//If in view and within view distance +GLOBAL_DATUM_INIT(in_view, /datum/ui_state/in_view, new) +/datum/ui_state/in_view/can_use_topic(src_object, mob/user) + return user.in_view_can_use_topic(src_object) // Call the individual mob-overridden procs. + +/mob/proc/in_view_can_use_topic(src_object) + return UI_CLOSE // Don't allow interaction by default. + +/mob/ghost/in_view_can_use_topic(src_object) + return UI_UPDATE //ghost can just watch + +/mob/living/in_view_can_use_topic(src_object) + . = shared_ui_interaction(src_object) + if(. > UI_CLOSE && loc) //must not be in nullspace. + . = min(., shared_living_ui_in_view(src_object)) // Check the distance and view... diff --git a/colonialmarines.dme b/colonialmarines.dme index b69a03136b78..9392e74febea 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -2362,6 +2362,7 @@ #include "code\modules\tgui\states\default.dm" #include "code\modules\tgui\states\hands.dm" #include "code\modules\tgui\states\human_adjacent.dm" +#include "code\modules\tgui\states\in_view.dm" #include "code\modules\tgui\states\inventory.dm" #include "code\modules\tgui\states\never.dm" #include "code\modules\tgui\states\new_player.dm"