From 95129525a0761c53a041d8fbaaef19675baf1ec2 Mon Sep 17 00:00:00 2001
From: ihatethisengine <115417687+ihatethisengine@users.noreply.github.com>
Date: Wed, 24 Apr 2024 23:09:31 +0300
Subject: [PATCH] More features and tweaks for broadcasting (#6115)
# About the pull request
Turned out that through cameras you can only see speech that you already
see without cameras. So I quickly found a way to fix it with the least
(for me) amount of crap code.
# Explain why it's good for the game
That's pretty much the point of broadcasting.
# Testing Photographs and Procedure
![2024-04-08_09-56-41](https://github.com/cmss13-devs/cmss13/assets/115417687/f7b28bd6-571b-4c4e-8c5c-337f25ea8b6e)
![Screenshot_2024-04-10_01-35-26](https://github.com/cmss13-devs/cmss13/assets/115417687/35b6cce1-ed2e-4270-973a-6e4387540ae2)
![Screenshot_2024-04-10_01-56-08](https://github.com/cmss13-devs/cmss13/assets/115417687/0bcab79b-2fb2-4364-91db-84f73bd58f24)
![Screenshot_2024-04-10_04-14-50](https://github.com/cmss13-devs/cmss13/assets/115417687/e85bb1a6-6642-4db1-a505-50e01248085a)
# Changelog
:cl: ihatethisengine
add: Combat Correspondent can broadcast speech and emotes.
add: Speech close enough to the camera will be shown above connected TVs
as abovehead messages
/:cl:
---------
Co-authored-by: Drathek <76988376+Drulikar@users.noreply.github.com>
---
.../dcs/signals/atom/signals_item.dm | 4 +-
code/__DEFINES/equipment.dm | 2 +
code/datums/emotes.dm | 10 ++++
code/game/machinery/camera/camera.dm | 2 +-
.../game/machinery/computer/camera_console.dm | 59 ++++++++++++++++---
code/game/objects/objs.dm | 3 +
code/modules/paperwork/photography.dm | 28 ++++++++-
code/modules/tgui/states.dm | 23 ++++++++
code/modules/tgui/states/in_view.dm | 15 +++++
colonialmarines.dme | 1 +
10 files changed, 134 insertions(+), 13 deletions(-)
create mode 100644 code/modules/tgui/states/in_view.dm
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"