Replaces Camera bug with Camera tablet app (#77713)

## About The Pull Request

Replaces the Traitor's Camera Bug item with an application that allows
you to view camera apps.
The difference between this app and the Security one is that it does not
have an access requirement, does not make noise (it didn't previously
due to a bug, fixed in this PR), and can be installed on PDAs.
This can also be installed from syndienet, which means nukies now have a
way to see the station cameras while off the ship.

Adds Syndicate app disks, which are made of Gold. This is only used by
the Camera app as of right now.

I also fixed some issues along the way;
- Camera tablet app now properly shows cameras
- It now properly makes the noises it is supposed to
- It clears the viewers properly on the ui being closed or the app being
exited.

Syndicate app disks ddelete their apps upon being transferred over (like
maintenance disks), trying to remain consistent with previous behavior.

I also made this for a [personal project I am currently working
on](https://hackmd.io/XLt5MoRvRxuhFbwtk4VAUA). I could just make this UI
be TGUI, but I thought it could be worth trying to turn it into a better
item first.

Tracking people is much simpler now- You choose anyone from a list of
people found on cameras, and it will try to find a camera that sees
them. It follows them until you close the app or switch camera.

https://github.com/tgstation/tgstation/assets/53777086/1536ebb9-0c4f-45bb-b593-f98791ea6d23

## Why It's Good For The Game

The Camera Bug is one of the worst traitor items as of current. You can
remotely shut off a single camera, something that can be done with basic
tools you are likely carrying around anyways, and uses an HTML clunky UI
to flip through cameras.
This new Traitor item makes it much easier to use, since the camera
consoles are something you are likely already used to.
It also means emagging a tablet is slightly more useful, and golden data
disks are pretty cool.

Unfortunately this means that they no longer grant illegal tech, because
otherwise you can simply clone the app infinitely for personal use, and
deconstruct the disk itself for tech. This can be grinded into gold, but
I think if we want an illegal tech item, it'll have to find a new host.

Closes https://github.com/tgstation/tgstation/issues/74839
Closes https://github.com/tgstation/tgstation/issues/39975
Closes https://github.com/tgstation/tgstation/issues/53820

## Changelog

🆑
balance: The Traitor's Camera bug is now a tablet app that works like a
silent camera console with an extra ability to track people through
their nearby cameras.
/🆑
This commit is contained in:
John Willard
2023-08-23 21:09:28 +12:00
committed by GitHub
parent 1a080df856
commit 17e16ad0c2
28 changed files with 159 additions and 413 deletions
@@ -30,7 +30,7 @@
examine_text += span_info("Alt-click to eject the intelliCard.")
return examine_text
/datum/computer_file/program/ai_restorer/kill_program()
/datum/computer_file/program/ai_restorer/kill_program(mob/user)
try_eject(forced = TRUE)
return ..()
@@ -32,7 +32,7 @@
target = null
error = "Connection to destination relay lost."
/datum/computer_file/program/ntnet_dos/kill_program()
/datum/computer_file/program/ntnet_dos/kill_program(mob/user)
if(target)
target.dos_sources.Remove(src)
target = null
@@ -19,7 +19,7 @@
DL_source = null
return ..()
/datum/computer_file/program/borg_monitor/kill_program()
/datum/computer_file/program/borg_monitor/kill_program(mob/user)
loglist = null //Not everything is saved if you close an app
DL_source = null
DL_progress = 0
@@ -99,9 +99,9 @@
)
data["cyborgs"] += list(cyborg_data)
data["DL_progress"] = DL_progress
data["borglog"] = loglist
return data
/datum/computer_file/program/borg_monitor/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
@@ -79,7 +79,7 @@
return FALSE
computer.crew_manifest_update = TRUE
/datum/computer_file/program/card_mod/kill_program()
/datum/computer_file/program/card_mod/kill_program(mob/user)
computer.crew_manifest_update = FALSE
var/obj/item/card/id/inserted_auth_card = computer.computer_id_slot
if(inserted_auth_card)
@@ -7,10 +7,4 @@
/datum/computer_file/program/maintenance
filetype = "MNT"
available_on_ntnet = FALSE
/datum/computer_file/program/maintenance/clone(rename = FALSE)
. = ..()
if(computer)
computer.remove_file(src)
if(disk_host)
disk_host.remove_file(src)
unique_copy = TRUE
@@ -162,6 +162,6 @@
return TRUE
return FALSE
/datum/computer_file/program/ntnetdownload/kill_program()
/datum/computer_file/program/ntnetdownload/kill_program(mob/user)
abort_file_download()
return ..()
@@ -200,7 +200,7 @@
channel.offline_clients.Remove(src)
channel.active_clients.Add(src)
/datum/computer_file/program/chatclient/kill_program()
/datum/computer_file/program/chatclient/kill_program(mob/user)
for(var/datum/ntnet_conversation/channel as anything in SSmodular_computers.chat_channels)
channel.go_offline(src)
active_channel = null
@@ -31,7 +31,7 @@
scan()
START_PROCESSING(SSfastprocess, src)
/datum/computer_file/program/radar/kill_program()
/datum/computer_file/program/radar/kill_program(mob/user)
objects = list()
selected = null
STOP_PROCESSING(SSfastprocess, src)
@@ -320,7 +320,7 @@
RegisterSignal(SSdcs, COMSIG_GLOB_NUKE_DEVICE_ARMED, PROC_REF(on_nuke_armed))
/datum/computer_file/program/radar/fission360/kill_program()
/datum/computer_file/program/radar/fission360/kill_program(mob/user)
UnregisterSignal(SSdcs, COMSIG_GLOB_NUKE_DEVICE_ARMED)
return ..()
@@ -14,18 +14,42 @@
tgui_id = "NtosSecurEye"
program_icon = "eye"
///Boolean on whether or not the app will make noise when flipping around the channels.
var/spying = FALSE
var/list/network = list("ss13")
///List of weakrefs of all users watching the program.
var/list/concurrent_users = list()
/// Weakref to the active camera
var/datum/weakref/camera_ref
/// The turf where the camera was last updated.
var/turf/last_camera_turf
var/list/concurrent_users = list()
// Stuff needed to render the map
var/atom/movable/screen/map_view/cam_screen
/// All the plane masters that need to be applied.
var/atom/movable/screen/background/cam_background
/datum/computer_file/program/secureye/New()
///Internal tracker used to find a specific person and keep them on cameras.
var/datum/trackable/internal_tracker
///Syndicate subtype that has no access restrictions and is available on Syndinet
/datum/computer_file/program/secureye/syndicate
filename = "syndeye"
filedesc = "SyndEye"
extended_desc = "This program allows for illegal access to security camera networks."
transfer_access = list()
available_on_ntnet = FALSE
available_on_syndinet = TRUE
requires_ntnet = FALSE
usage_flags = PROGRAM_ALL
unique_copy = TRUE
network = list("ss13", "mine", "rd", "labor", "ordnance", "minisat")
spying = TRUE
/datum/computer_file/program/secureye/on_install(datum/computer_file/source, obj/item/modular_computer/computer_installing)
. = ..()
// Map name has to start and end with an A-Z character,
// and definitely NOT with a square bracket or even a number.
@@ -40,30 +64,33 @@
cam_background = new
cam_background.assigned_map = map_name
cam_background.del_on_map_removal = FALSE
RegisterSignal(src, COMSIG_TRACKABLE_TRACKING_TARGET, PROC_REF(on_track_target))
/datum/computer_file/program/secureye/Destroy()
QDEL_NULL(cam_screen)
QDEL_NULL(cam_background)
QDEL_NULL(internal_tracker)
last_camera_turf = null
return ..()
/datum/computer_file/program/secureye/kill_program(mob/user)
if(user)
ui_close(user)
return ..()
/datum/computer_file/program/secureye/ui_interact(mob/user, datum/tgui/ui)
// Update UI
ui = SStgui.try_update_ui(user, src, ui)
// Update the camera, showing static if necessary and updating data if the location has moved.
update_active_camera_screen()
if(!ui)
var/user_ref = REF(user)
var/is_living = isliving(user)
// Ghosts shouldn't count towards concurrent users, which produces
// an audible terminal_on click.
if(is_living)
concurrent_users += user_ref
// Register map objects
cam_screen.display_to(user)
user.client.register_map_obj(cam_background)
return ..()
var/user_ref = REF(user)
var/is_living = isliving(user)
// Ghosts shouldn't count towards concurrent users, which produces
// an audible terminal_on click.
if(is_living)
concurrent_users += user_ref
// Register map objects
cam_screen.display_to(user)
user.client.register_map_obj(cam_background)
/datum/computer_file/program/secureye/ui_status(mob/user)
. = ..()
@@ -86,6 +113,7 @@
/datum/computer_file/program/secureye/ui_static_data(mob/user)
var/list/data = list()
data["mapRef"] = cam_screen.assigned_map
data["can_spy"] = !!spying
var/list/cameras = get_camera_list(network)
data["cameras"] = list()
for(var/i in cameras)
@@ -100,22 +128,49 @@
. = ..()
if(.)
return
if(action == "switch_camera")
var/c_tag = format_text(params["name"])
var/list/cameras = get_camera_list(network)
var/obj/machinery/camera/selected_camera = cameras[c_tag]
camera_ref = WEAKREF(selected_camera)
playsound(src, get_sfx(SFX_TERMINAL_TYPE), 25, FALSE)
switch(action)
if("switch_camera")
var/c_tag = format_text(params["name"])
var/list/cameras = get_camera_list(network)
var/obj/machinery/camera/selected_camera = cameras[c_tag]
camera_ref = WEAKREF(selected_camera)
if(!spying)
playsound(computer, get_sfx(SFX_TERMINAL_TYPE), 25, FALSE)
if(!selected_camera)
return TRUE
if(internal_tracker && internal_tracker.tracking)
internal_tracker.set_tracking(FALSE)
if(!selected_camera)
update_active_camera_screen()
return TRUE
update_active_camera_screen()
if("start_tracking")
if(!internal_tracker)
internal_tracker = new(src)
internal_tracker.set_tracked_mob(usr)
return TRUE
return TRUE
/datum/computer_file/program/secureye/proc/on_track_target(datum/trackable/source, mob/living/target)
SIGNAL_HANDLER
var/datum/camerachunk/target_camerachunk = GLOB.cameranet.getTurfVis(get_turf(target))
if(!target_camerachunk)
CRASH("[src] was able to track [target] through /datum/trackable, but was not on a visible turf to cameras.")
for(var/obj/machinery/camera/cameras as anything in target_camerachunk.cameras["[target.z]"])
var/found_target = locate(target) in cameras.can_see()
if(!found_target)
continue
var/new_camera = WEAKREF(cameras)
if(camera_ref == new_camera)
return
camera_ref = new_camera
update_active_camera_screen()
return
/datum/computer_file/program/secureye/ui_close(mob/user)
. = ..()
//don't track anyone while we're shutting off.
if(internal_tracker && internal_tracker.tracking)
internal_tracker.set_tracking(FALSE)
var/user_ref = REF(user)
var/is_living = isliving(user)
// Living creature or not, we remove you anyway.
@@ -125,7 +180,9 @@
// Turn off the console
if(length(concurrent_users) == 0 && is_living)
camera_ref = null
playsound(src, 'sound/machines/terminal_off.ogg', 25, FALSE)
last_camera_turf = null
if(!spying)
playsound(computer, 'sound/machines/terminal_off.ogg', 25, FALSE)
/datum/computer_file/program/secureye/proc/update_active_camera_screen()
var/obj/machinery/camera/active_camera = camera_ref?.resolve()
@@ -19,7 +19,7 @@
. = ..()
set_frequency(signal_frequency)
/datum/computer_file/program/signal_commander/kill_program()
/datum/computer_file/program/signal_commander/kill_program(mob/user)
. = ..()
SSradio.remove_object(computer, signal_frequency)
@@ -22,7 +22,7 @@
refresh()
/// Apparently destroy calls this [/datum/computer_file/Destroy]. Here just to clean our references.
/datum/computer_file/program/supermatter_monitor/kill_program()
/datum/computer_file/program/supermatter_monitor/kill_program(mob/user)
for(var/supermatter in supermatters)
clear_supermatter(supermatter)
return ..()