diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index f04aba5a25..5b39bc618f 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -13,12 +13,13 @@
var/list/network = list()
var/datum/tgui_module/camera/camera
+ var/camera_datum_type = /datum/tgui_module/camera
/obj/machinery/computer/security/Initialize()
. = ..()
if(!LAZYLEN(network))
network = get_default_networks()
- camera = new(src, network)
+ camera = new camera_datum_type(src, network)
/obj/machinery/computer/security/proc/get_default_networks()
. = using_map.station_networks.Copy()
@@ -67,19 +68,41 @@
density = 0
circuit = null
+GLOBAL_LIST_EMPTY(entertainment_screens)
/obj/machinery/computer/security/telescreen/entertainment
name = "entertainment monitor"
desc = "Damn, why do they never have anything interesting on these things?"
- icon = 'icons/obj/status_display.dmi'
- icon_screen = "entertainment"
+ icon = 'icons/obj/entertainment_monitor.dmi'
+ icon_state = "screen"
+ icon_screen = null
light_color = "#FFEEDB"
light_range_on = 2
network = list(NETWORK_THUNDER)
circuit = /obj/item/weapon/circuitboard/security/telescreen/entertainment
+ camera_datum_type = /datum/tgui_module/camera/bigscreen
+
var/obj/item/device/radio/radio = null
+ var/obj/effect/overlay/vis/pinboard
+ var/weakref/showing
/obj/machinery/computer/security/telescreen/entertainment/Initialize()
+ GLOB.entertainment_screens += src
+
+ var/static/icon/mask = icon('icons/obj/entertainment_monitor.dmi', "mask")
+
+ add_overlay("glass")
+
+ pinboard = new()
+ pinboard.icon = icon
+ pinboard.icon_state = "pinboard"
+ pinboard.layer = 0.1
+ pinboard.vis_flags = VIS_UNDERLAY|VIS_INHERIT_ID|VIS_INHERIT_PLANE
+ pinboard.appearance_flags = KEEP_TOGETHER
+ pinboard.add_filter("screen cutter", 1, alpha_mask_filter(icon = mask))
+ vis_contents += pinboard
+
. = ..()
+
radio = new(src)
radio.listening = TRUE
radio.broadcasting = FALSE
@@ -87,13 +110,44 @@
radio.canhear_range = 7 // Same as default sight range.
power_change()
+/obj/machinery/computer/security/telescreen/entertainment/Destroy()
+ if(showing)
+ stop_showing()
+ vis_contents.Cut()
+ qdel_null(pinboard)
+ qdel_null(radio)
+ return ..()
+
+/obj/machinery/computer/security/telescreen/entertainment/Click(location, control, params)
+ attack_hand(usr)
+
+/obj/machinery/computer/security/telescreen/entertainment/update_icon()
+ return // NUH
+
+/obj/machinery/computer/security/telescreen/entertainment/proc/show_thing(atom/thing)
+ if(showing)
+ stop_showing()
+ if(stat & NOPOWER)
+ return
+ showing = weakref(thing)
+ pinboard.vis_contents = list(thing)
+
+/obj/machinery/computer/security/telescreen/entertainment/proc/stop_showing()
+ // Reverse of the above
+ pinboard.vis_contents = null
+ showing = null
+
+/obj/machinery/computer/security/telescreen/entertainment/proc/maybe_stop_showing(weakref/thingref)
+ if(showing == thingref)
+ stop_showing()
+
/obj/machinery/computer/security/telescreen/entertainment/power_change()
..()
- if(radio)
- if(stat & NOPOWER)
- radio.on = FALSE
- else
- radio.on = TRUE
+ if(stat & NOPOWER)
+ radio?.on = FALSE
+ stop_showing()
+ else
+ radio?.on = TRUE
/obj/machinery/computer/security/wooden_tv
name = "security camera monitor"
diff --git a/code/game/objects/items/devices/tvcamera.dm b/code/game/objects/items/devices/tvcamera.dm
index acf4b7cc3a..b7d7933a88 100644
--- a/code/game/objects/items/devices/tvcamera.dm
+++ b/code/game/objects/items/devices/tvcamera.dm
@@ -8,6 +8,8 @@
var/channel = "NCS Northern Star News Feed"
var/obj/machinery/camera/network/thunder/camera
var/obj/item/device/radio/radio
+ var/weakref/showing
+ var/showing_name
/obj/item/device/tvcamera/New()
..()
@@ -45,9 +47,14 @@
/obj/item/device/tvcamera/attack_self(mob/user)
add_fingerprint(user)
user.set_machine(src)
+ show_ui(user)
+
+/obj/item/device/tvcamera/proc/show_ui(mob/user)
var/dat = list()
dat += "Channel name is: [channel ? channel : "unidentified broadcast"]
"
dat += "Video streaming is [camera.status ? "on" : "off"]
"
+ if(camera.status && showing_name)
+ dat += "- You're showing [showing_name] to your viewers.
"
dat += "Mic is [radio.broadcasting ? "on" : "off"]
"
dat += "Sound is being broadcasted on frequency [format_frequency(radio.frequency)] ([get_frequency_name(radio.frequency)])
"
var/datum/browser/popup = new(user, "Hovercamera", "Eye Buddy", 300, 390, src)
@@ -67,8 +74,12 @@
camera.set_status(!camera.status)
if(camera.status)
to_chat(usr,"Video streaming activated. Broadcasting on channel '[channel]'")
+ show_tvs(loc)
else
to_chat(usr,"Video streaming deactivated.")
+ hide_tvs()
+ for(var/obj/machinery/computer/security/telescreen/entertainment/ES as anything in GLOB.entertainment_screens)
+ ES.stop_showing()
update_icon()
if(href_list["sound"])
radio.ToggleBroadcast()
@@ -79,6 +90,50 @@
if(!href_list["close"])
attack_self(usr)
+/obj/item/device/tvcamera/proc/show_tvs(atom/thing)
+ if(showing)
+ hide_tvs(showing)
+
+ showing = weakref(thing)
+ showing_name = "[thing]"
+ for(var/obj/machinery/computer/security/telescreen/entertainment/ES as anything in GLOB.entertainment_screens)
+ ES.show_thing(thing)
+
+ START_PROCESSING(SSobj, src)
+
+/obj/item/device/tvcamera/proc/hide_tvs()
+ if(!showing)
+ return
+ for(var/obj/machinery/computer/security/telescreen/entertainment/ES as anything in GLOB.entertainment_screens)
+ ES.maybe_stop_showing(showing)
+ STOP_PROCESSING(SSobj, src)
+ showing = null
+ showing_name = null
+
+/obj/item/device/tvcamera/Moved(atom/old_loc, direction, forced = FALSE, movetime)
+ . = ..()
+ if(camera.status && loc != old_loc)
+ show_tvs(loc)
+
+/obj/item/device/tvcamera/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(camera.status && !isturf(target))
+ show_tvs(target)
+ user.visible_message("[user] aims [src] at [target].", "You aim [src] at [target].")
+ if(user.machine == src)
+ show_ui(user) // refresh the UI
+
+/obj/item/device/tvcamera/process()
+ if(!showing)
+ return PROCESS_KILL
+
+ var/atom/A = showing.resolve()
+ if(!A || QDELETED(A))
+ show_tvs(loc)
+
+ if(get_dist(get_turf(src), get_turf(A)) > 5)
+ show_tvs(loc)
+
/obj/item/device/tvcamera/update_icon()
..()
if(camera.status)
diff --git a/code/modules/tgui/modules/camera.dm b/code/modules/tgui/modules/camera.dm
index 86eb923c1c..abfef14be1 100644
--- a/code/modules/tgui/modules/camera.dm
+++ b/code/modules/tgui/modules/camera.dm
@@ -296,3 +296,7 @@
/datum/tgui_module/camera/ntos/hacked/New(host)
. = ..(host, using_map.station_networks.Copy())
+
+/datum/tgui_module/camera/bigscreen/tgui_state(mob/user)
+ return GLOB.tgui_physical_state_bigscreen
+
\ No newline at end of file
diff --git a/code/modules/tgui/states.dm b/code/modules/tgui/states.dm
index 01b8ae1a84..5a66efc090 100644
--- a/code/modules/tgui/states.dm
+++ b/code/modules/tgui/states.dm
@@ -85,7 +85,7 @@
*
* Check the distance for a living mob.
* Really only used for checks outside the context of a mob.
- * Otherwise, use shared_living_ui_distance().
+ * Otherwise, use shared_living_tgui_distance().
*
* required src_object The object which owns the UI.
* required user mob The mob who opened/is using the UI.
@@ -119,10 +119,26 @@
return STATUS_DISABLED
return STATUS_CLOSE // Otherwise, we got nothing.
-/mob/living/carbon/human/shared_living_tgui_distance(atom/movable/src_object, viewcheck = TRUE)
- if((TK in mutations) && (get_dist(src, src_object) <= 2))
+/**
+ * public
+ *
+ * Distance versus interaction check, with max'd update range.
+ *
+ * required src_object atom/movable The object which owns the UI.
+ *
+ * return UI_state The state of the UI.
+ */
+/mob/living/proc/shared_living_tgui_distance_bigscreen(atom/movable/src_object, viewcheck = TRUE)
+ // If the object is obscured, close it.
+ if(viewcheck && !(src_object in view(src)))
+ return STATUS_CLOSE
+
+ var/dist = get_dist(src_object, src)
+ if(dist <= 1) // Open and interact if 1-0 tiles away.
return STATUS_INTERACTIVE
- return ..()
+ else if(dist <= world.view)
+ return STATUS_UPDATE
+ return STATUS_CLOSE // Otherwise, we got nothing.
// Topic Extensions for old UIs
/datum/proc/CanUseTopic(var/mob/user, var/datum/tgui_state/state)
diff --git a/code/modules/tgui/states/physical.dm b/code/modules/tgui/states/physical.dm
index 30c67906ae..9712090b5d 100644
--- a/code/modules/tgui/states/physical.dm
+++ b/code/modules/tgui/states/physical.dm
@@ -47,3 +47,29 @@ GLOBAL_DATUM_INIT(tgui_physical_obscured_state, /datum/tgui_state/physical_obscu
/mob/living/silicon/ai/physical_obscured_can_use_topic(src_object)
return STATUS_UPDATE // AIs are not physical.
+
+ /**
+ * tgui state: physical_state_bigscreen
+ *
+ * Short-circuits the default state to only check physical distance,
+ * but allows updates out to the full size of the screen.
+ **/
+
+GLOBAL_DATUM_INIT(tgui_physical_state_bigscreen, /datum/tgui_state/physical_bigscreen, new)
+
+/datum/tgui_state/physical_bigscreen/can_use_topic(src_object, mob/user)
+ . = user.shared_tgui_interaction(src_object)
+ if(. > STATUS_CLOSE)
+ return min(., user.physical_can_use_tgui_topic_bigscreen(src_object))
+
+/mob/proc/physical_can_use_tgui_topic_bigscreen(src_object)
+ return STATUS_CLOSE
+
+/mob/living/physical_can_use_tgui_topic_bigscreen(src_object)
+ return shared_living_tgui_distance_bigscreen(src_object)
+
+/mob/living/silicon/physical_can_use_tgui_topic_bigscreen(src_object)
+ return max(STATUS_UPDATE, shared_living_tgui_distance_bigscreen(src_object)) // Silicons can always see.
+
+/mob/living/silicon/ai/physical_can_use_tgui_topic(src_object)
+ return STATUS_UPDATE // AIs are not physical.
\ No newline at end of file
diff --git a/icons/obj/entertainment_monitor.dmi b/icons/obj/entertainment_monitor.dmi
new file mode 100644
index 0000000000..40250b33e0
Binary files /dev/null and b/icons/obj/entertainment_monitor.dmi differ