[TGUI] Camera Consoles (Actual Magic) (#13750)

* TGUI Camera Consoles

* inb4 I fail my own CI routines

* Makes it so AIs cant use cam consoles

* Refactors camera bugs
This commit is contained in:
AffectedArc07
2020-09-19 09:55:33 -04:00
committed by GitHub
parent ce2f0d401d
commit a896bc825f
13 changed files with 490 additions and 639 deletions
+21
View File
@@ -472,3 +472,24 @@
if(!C || !C.prefs.windowflashing)
return
winset(C, "mainwindow", "flash=5")
/**
* Get a bounding box of a list of atoms.
*
* Arguments:
* - atoms - List of atoms. Can accept output of view() and range() procs.
*
* Returns: list(x1, y1, x2, y2)
*/
/proc/get_bbox_of_atoms(list/atoms)
var/list/list_x = list()
var/list/list_y = list()
for(var/_a in atoms)
var/atom/a = _a
list_x += a.x
list_y += a.y
return list(
min(list_x),
min(list_y),
max(list_x),
max(list_y))
+170
View File
@@ -0,0 +1,170 @@
/client
/**
* Assoc list with all the active maps - when a screen obj is added to
* a map, it's put in here as well.
*
* Format: list(<mapname> = list(/obj/screen))
*/
var/list/screen_maps = list()
/obj/screen
/**
* Map name assigned to this object.
* Automatically set by /client/proc/add_obj_to_map.
*/
var/assigned_map
/**
* Mark this object as garbage-collectible after you clean the map
* it was registered on.
*
* This could probably be changed to be a proc, for conditional removal.
* But for now, this works.
*/
var/del_on_map_removal = TRUE
/**
* A screen object, which acts as a container for turfs and other things
* you want to show on the map, which you usually attach to "vis_contents".
*/
/obj/screen/map_view
// Map view has to be on the lowest plane to enable proper lighting
layer = GAME_PLANE
plane = GAME_PLANE
/**
* A generic background object.
* It is also implicitly used to allocate a rectangle on the map, which will
* be used for auto-scaling the map.
*/
/obj/screen/background
name = "background"
icon = 'icons/mob/map_backgrounds.dmi'
icon_state = "clear"
layer = GAME_PLANE
plane = GAME_PLANE
/**
* Sets screen_loc of this screen object, in form of point coordinates,
* with optional pixel offset (px, py).
*
* If applicable, "assigned_map" has to be assigned before this proc call.
*/
/obj/screen/proc/set_position(x, y, px = 0, py = 0)
if(assigned_map)
screen_loc = "[assigned_map]:[x]:[px],[y]:[py]"
else
screen_loc = "[x]:[px],[y]:[py]"
/**
* Sets screen_loc to fill a rectangular area of the map.
*
* If applicable, "assigned_map" has to be assigned before this proc call.
*/
/obj/screen/proc/fill_rect(x1, y1, x2, y2)
if(assigned_map)
screen_loc = "[assigned_map]:[x1],[y1] to [x2],[y2]"
else
screen_loc = "[x1],[y1] to [x2],[y2]"
/**
* Registers screen obj with the client, which makes it visible on the
* assigned map, and becomes a part of the assigned map's lifecycle.
*/
/client/proc/register_map_obj(obj/screen/screen_obj)
if(!screen_obj.assigned_map)
CRASH("Can't register [screen_obj] without 'assigned_map' property.")
if(!screen_maps[screen_obj.assigned_map])
screen_maps[screen_obj.assigned_map] = list()
// NOTE: Possibly an expensive operation
var/list/screen_map = screen_maps[screen_obj.assigned_map]
if(!screen_map.Find(screen_obj))
screen_map += screen_obj
if(!screen.Find(screen_obj))
screen += screen_obj
/**
* Clears the map of registered screen objects.
*
* Not really needed most of the time, as the client's screen list gets reset
* on relog. any of the buttons are going to get caught by garbage collection
* anyway. they're effectively qdel'd.
*/
/client/proc/clear_map(map_name)
if(!map_name || !(map_name in screen_maps))
return FALSE
for(var/obj/screen/screen_obj in screen_maps[map_name])
screen_maps[map_name] -= screen_obj
if(screen_obj.del_on_map_removal)
qdel(screen_obj)
screen_maps -= map_name
/**
* Clears all the maps of registered screen objects.
*/
/client/proc/clear_all_maps()
for(var/map_name in screen_maps)
clear_map(map_name)
/**
* Creates a popup window with a basic map element in it, without any
* further initialization.
*
* Ratio is how many pixels by how many pixels (keep it simple).
*
* Returns a map name.
*/
/client/proc/create_popup(name, ratiox = 100, ratioy = 100)
winclone(src, "popupwindow", name)
var/list/winparams = list()
winparams["size"] = "[ratiox]x[ratioy]"
winparams["on-close"] = "handle-popup-close [name]"
winset(src, "[name]", list2params(winparams))
winshow(src, "[name]", 1)
var/list/params = list()
params["parent"] = "[name]"
params["type"] = "map"
params["size"] = "[ratiox]x[ratioy]"
params["anchor1"] = "0,0"
params["anchor2"] = "[ratiox],[ratioy]"
winset(src, "[name]_map", list2params(params))
return "[name]_map"
/**
* Create the popup, and get it ready for generic use by giving
* it a background.
*
* Width and height are multiplied by 64 by default.
*/
/client/proc/setup_popup(popup_name, width = 9, height = 9, \
tilesize = 2, bg_icon)
if(!popup_name)
return
clear_map("[popup_name]_map")
var/x_value = world.icon_size * tilesize * width
var/y_value = world.icon_size * tilesize * height
var/map_name = create_popup(popup_name, x_value, y_value)
var/obj/screen/background/background = new
background.assigned_map = map_name
background.fill_rect(1, 1, width, height)
if(bg_icon)
background.icon_state = bg_icon
register_map_obj(background)
return map_name
/**
* Closes a popup.
*/
/client/proc/close_popup(popup)
winshow(src, popup, 0)
handle_popup_close(popup)
/**
* When the popup closes in any way (player or proc call) it calls this.
*/
/client/verb/handle_popup_close(window_id as text)
set hidden = TRUE
clear_map("[window_id]_map")
+1 -1
View File
@@ -1183,7 +1183,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
/datum/uplink_item/stealthy_tools/camera_bug
name = "Camera Bug"
desc = "Enables you to view all cameras on the network and track a target. Bugging cameras allows you to disable them remotely."
desc = "Enables you to view all cameras on the network to track a target."
reference = "CB"
item = /obj/item/camera_bug
cost = 1
-19
View File
@@ -20,7 +20,6 @@
anchored = TRUE
var/start_active = FALSE //If it ignores the random chance to start broken on round start
var/invuln = null
var/obj/item/camera_bug/bug = null
var/obj/item/camera_assembly/assembly = null
//OTHER
@@ -58,11 +57,6 @@
SStgui.close_uis(wires)
toggle_cam(null, FALSE) //kick anyone viewing out
QDEL_NULL(assembly)
if(istype(bug))
bug.bugged_cameras -= c_tag
if(bug.current == src)
bug.current = null
bug = null
QDEL_NULL(wires)
GLOB.cameranet.removeCamera(src) //Will handle removal from the camera network and the chunks, so we don't need to worry about that
GLOB.cameranet.cameras -= src
@@ -201,19 +195,6 @@
to_chat(O, "[U] holds \a [itemname] up to one of the cameras ...")
O << browse(text("<HTML><HEAD><TITLE>[]</TITLE></HEAD><BODY><TT>[]</TT></BODY></HTML>", itemname, info), text("window=[]", itemname))
else if(istype(I, /obj/item/camera_bug))
if(!can_use())
to_chat(user, "<span class='notice'>Camera non-functional.</span>")
return
if(istype(bug))
to_chat(user, "<span class='notice'>Camera bug removed.</span>")
bug.bugged_cameras -= c_tag
bug = null
else
to_chat(user, "<span class='notice'>Camera bugged.</span>")
bug = I
bug.bugged_cameras[c_tag] = src
else if(istype(I, /obj/item/laser_pointer))
var/obj/item/laser_pointer/L = I
L.laser_act(src, user)
+147 -265
View File
@@ -10,75 +10,167 @@
var/mapping = 0 // For the overview file (overview.dm), not used on this page
var/list/network = list()
var/list/available_networks = list()
var/list/watchers = list() //who's using the console, associated with the camera they're on.
var/obj/machinery/camera/active_camera
var/list/watchers = list()
/obj/machinery/computer/security/New() // Lists existing networks and their required access. Format: available_networks[<name>] = list(<access>)
generate_network_access()
..()
// Stuff needed to render the map
var/map_name
var/const/default_map_size = 15
var/obj/screen/map_view/cam_screen
/// All the plane masters that need to be applied.
var/list/cam_plane_masters
var/obj/screen/background/cam_background
/obj/machinery/computer/security/proc/generate_network_access()
available_networks["SS13"] = list(ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["Telecomms"] = list(ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["Research Outpost"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["Mining Outpost"] = list(ACCESS_QM,ACCESS_HOP,ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["Research"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["Prison"] = list(ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["Labor Camp"] = list(ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["Interrogation"] = list(ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["Atmosphere Alarms"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["Fire Alarms"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["Power Alarms"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["Supermatter"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["MiniSat"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["Singularity"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["Anomaly Isolation"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["Toxins"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["Telepad"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["TestChamber"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN)
available_networks["ERT"] = list(ACCESS_CENT_SPECOPS_COMMANDER,ACCESS_CENT_COMMANDER)
available_networks["CentComm"] = list(ACCESS_CENT_SECURITY,ACCESS_CENT_COMMANDER)
available_networks["Thunderdome"] = list(ACCESS_CENT_THUNDER,ACCESS_CENT_COMMANDER)
// Parent object this camera is assigned to. Used for camera bugs
var/atom/movable/parent
/obj/machinery/computer/security/tgui_host()
return parent ? parent : src
/obj/machinery/computer/security/Initialize()
. = ..()
// Initialize map objects
map_name = "camera_console_[UID()]_map"
cam_screen = new
cam_screen.name = "screen"
cam_screen.assigned_map = map_name
cam_screen.del_on_map_removal = FALSE
cam_screen.screen_loc = "[map_name]:1,1"
cam_plane_masters = list()
for(var/plane in subtypesof(/obj/screen/plane_master))
var/obj/screen/instance = new plane()
instance.assigned_map = map_name
instance.del_on_map_removal = FALSE
instance.screen_loc = "[map_name]:CENTER"
cam_plane_masters += instance
cam_background = new
cam_background.assigned_map = map_name
cam_background.del_on_map_removal = FALSE
/obj/machinery/computer/security/Destroy()
if(watchers.len)
for(var/mob/M in watchers)
M.unset_machine() //to properly reset the view of the users if the console is deleted.
qdel(cam_screen)
QDEL_LIST(cam_plane_masters)
qdel(cam_background)
return ..()
/obj/machinery/computer/security/proc/isCameraFarAway(obj/machinery/camera/C)
var/turf/consoleturf = get_turf(src)
var/turf/cameraturf = get_turf(C)
if((is_away_level(cameraturf.z) || is_away_level(consoleturf.z)) && !atoms_share_level(cameraturf, consoleturf)) //can only recieve away mission cameras on away missions
/obj/machinery/computer/security/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
// Update UI
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
// Show static if can't use the camera
if(!active_camera?.can_use())
show_camera_static()
if(!ui)
var/user_uid = user.UID()
var/is_living = isliving(user)
// Ghosts shouldn't count towards concurrent users, which produces
// an audible terminal_on click.
if(is_living)
watchers += user_uid
// Turn on the console
if(length(watchers) == 1 && is_living)
playsound(src, 'sound/machines/terminal_on.ogg', 25, FALSE)
use_power(active_power_usage)
// Register map objects
user.client.register_map_obj(cam_screen)
for(var/plane in cam_plane_masters)
user.client.register_map_obj(plane)
user.client.register_map_obj(cam_background)
// Open UI
ui = new(user, src, ui_key, "CameraConsole", name, 870, 708, master_ui, state)
ui.open()
/obj/machinery/computer/security/tgui_data()
var/list/data = list()
data["network"] = network
data["activeCamera"] = null
if(active_camera)
data["activeCamera"] = list(
name = active_camera.c_tag,
status = active_camera.status,
)
return data
/obj/machinery/computer/security/tgui_static_data()
var/list/data = list()
data["mapRef"] = map_name
var/list/cameras = get_available_cameras()
data["cameras"] = list()
for(var/i in cameras)
var/obj/machinery/camera/C = cameras[i]
data["cameras"] += list(list(
name = C.c_tag,
))
return data
/obj/machinery/computer/security/tgui_act(action, params)
if(..())
return
if(action == "switch_camera")
var/c_tag = params["name"]
var/list/cameras = get_available_cameras()
var/obj/machinery/camera/C = cameras[c_tag]
active_camera = C
playsound(src, get_sfx("terminal_type"), 25, FALSE)
// Show static if can't use the camera
if(!active_camera?.can_use())
show_camera_static()
return TRUE
var/list/visible_turfs = list()
for(var/turf/T in (C.isXRay() \
? range(C.view_range, C) \
: view(C.view_range, C)))
visible_turfs += T
var/list/bbox = get_bbox_of_atoms(visible_turfs)
var/size_x = bbox[3] - bbox[1] + 1
var/size_y = bbox[4] - bbox[2] + 1
cam_screen.vis_contents = visible_turfs
cam_background.icon_state = "clear"
cam_background.fill_rect(1, 1, size_x, size_y)
return TRUE
/obj/machinery/computer/security/check_eye(mob/user)
if((stat & (NOPOWER|BROKEN)) || user.incapacitated() || !user.has_vision())
user.unset_machine()
return
if(!(user in watchers))
user.unset_machine()
return
if(!watchers[user])
user.unset_machine()
return
var/obj/machinery/camera/C = watchers[user]
if(isCameraFarAway(C))
user.unset_machine()
return
if(!can_access_camera(C, user))
user.unset_machine()
/obj/machinery/computer/security/on_unset_machine(mob/user)
watchers.Remove(user)
user.reset_perspective(null)
// Returns the list of cameras accessible from this computer
/obj/machinery/computer/security/proc/get_available_cameras()
var/list/L = list()
for (var/obj/machinery/camera/C in GLOB.cameranet.cameras)
if((is_away_level(z) || is_away_level(C.z)) && (C.z != z))//if on away mission, can only receive feed from same z_level cameras
continue
L.Add(C)
var/list/D = list()
for(var/obj/machinery/camera/C in L)
if(!C.network)
stack_trace("Camera in a cameranet has no camera network")
continue
if(!(islist(C.network)))
stack_trace("Camera in a cameranet has a non-list camera network")
continue
var/list/tempnetwork = C.network & network
if(tempnetwork.len)
D["[C.c_tag]"] = C
return D
/obj/machinery/computer/security/attack_hand(mob/user)
if(stat || ..())
user.unset_machine()
return
ui_interact(user)
tgui_interact(user)
/obj/machinery/computer/security/attack_ai(mob/user)
to_chat(user, "<span class='notice'>You realise its kind of stupid to access a camera console when you have the entire camera network at your metaphorical fingertips</span>")
return
/obj/machinery/computer/security/proc/show_camera_static()
cam_screen.vis_contents.Cut()
cam_background.icon_state = "scanline2"
cam_background.fill_rect(1, 1, default_map_size, default_map_size)
/obj/machinery/computer/security/telescreen/multitool_act(mob/user, obj/item/I)
. = TRUE
@@ -99,216 +191,6 @@
if("West")
pixel_x = -32
/obj/machinery/computer/security/emag_act(user as mob)
if(!emagged)
emagged = 1
to_chat(user, "<span class='notice'>You have authorized full network access!</span>")
attack_hand(user)
else
attack_hand(user)
/obj/machinery/computer/security/proc/get_user_access(mob/user)
var/list/access = list()
if(emagged)
access = get_all_accesses() // Assume captain level access when emagged
else if(ishuman(user))
access = user.get_access()
else if((isAI(user) || isrobot(user)) && CanUseTopic(user, GLOB.default_state) == STATUS_INTERACTIVE)
access = get_all_accesses() // Assume captain level access when AI
else if(user.can_admin_interact())
access = get_all_accesses()
return access
/obj/machinery/computer/security/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "sec_camera.tmpl", "Camera Console", 900, 800)
// adding a template with the key "mapContent" enables the map ui functionality
ui.add_template("mapContent", "sec_camera_map_content.tmpl")
// adding a template with the key "mapHeader" replaces the map header content
ui.add_template("mapHeader", "sec_camera_map_header.tmpl")
// Send nanomaps
var/datum/asset/nanomaps = get_asset_datum(/datum/asset/simple/nanomaps)
nanomaps.send(user)
ui.open()
/obj/machinery/computer/security/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
var/data[0]
var/list/cameras = list()
for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
if(isCameraFarAway(C))
continue
if(!can_access_camera(C, user))
continue
cameras[++cameras.len] = C.nano_structure()
for(var/i = cameras.len, i > 0, i--) //based off /proc/camera_sort, sorts cameras alphabetically for the UI
for(var/j = 1 to i - 1)
var/a = cameras[j]
var/b = cameras[j + 1]
if(sorttext(a["name"], b["name"]) < 0)
cameras.Swap(j, j + 1)
data["cameras"] = cameras
var/list/access = get_user_access(user)
if(emagged)
data["emagged"] = 1
var/list/networks_list = list()
// Loop through the ID's permission, and check which networks the ID has access to.
for(var/net in available_networks) // Loop through networks.
for(var/req in available_networks[net]) // Loop through access levels of the networks.
if(req in access)
if(net in network) // Checks if the network is currently active.
networks_list.Add(list(list("name" = net, "active" = 1)))
else
networks_list.Add(list(list("name" = net, "active" = 0)))
break
if(networks_list.len)
data["networks"] = networks_list
data["current"] = null
if(watchers[user])
var/obj/machinery/camera/watched = watchers[user]
data["current"] = watched.nano_structure()
return data
/obj/machinery/computer/security/Topic(href, href_list)
if(..())
usr.unset_machine()
return 1
if(href_list["switchTo"])
var/obj/machinery/camera/C = locate(href_list["switchTo"]) in GLOB.cameranet.cameras
if(!C)
return 1
switch_to_camera(usr, C)
else if(href_list["reset"])
usr.unset_machine()
else if(href_list["activate"]) // Activate: enable or disable networks
var/net = href_list["activate"] // Network to be enabled or disabled.
var/active = href_list["active"] // Is the network currently active.
var/list/access = get_user_access(usr)
for(var/a in available_networks[net])
if(a in access) // Re-check for authorization.
if(text2num(active) == 1)
network -= net
break
else
network += net
break
SSnanoui.update_uis(src)
// Check if camera is accessible when jumping
/obj/machinery/computer/security/proc/can_access_camera(var/obj/machinery/camera/C, var/mob/M)
if(CanUseTopic(M, GLOB.default_state) != STATUS_INTERACTIVE || M.incapacitated() || !M.has_vision())
return 0
if(isrobot(M))
var/list/viewing = viewers(src)
if(!viewing.Find(M))
return 0
if(isAI(M))
var/mob/living/silicon/ai/A = M
if(!A.is_in_chassis())
return 0
if(!issilicon(M) && !Adjacent(M))
return 0
var/list/shared_networks = network & C.network
if(!shared_networks.len || !C.can_use())
return 0
return 1
// Switching to cameras
/obj/machinery/computer/security/proc/switch_to_camera(var/mob/user, var/obj/machinery/camera/C)
if(!can_access_camera(C, user))
user.unset_machine()
return 1
if(isAI(user))
var/mob/living/silicon/ai/A = user
A.eyeobj.setLoc(get_turf(C))
A.client.eye = A.eyeobj
else
user.reset_perspective(C)
watchers[user] = C
use_power(50)
//Camera control: moving.
/obj/machinery/computer/security/proc/jump_on_click(var/mob/user, var/A)
if(user.machine != src)
return
var/obj/machinery/camera/jump_to
if(istype(A, /obj/machinery/camera))
jump_to = A
else if(ismob(A))
if(ishuman(A))
var/mob/living/carbon/human/H = A
jump_to = locate() in H.head
else if(isrobot(A))
var/mob/living/silicon/robot/R = A
jump_to = R.camera
else if(isobj(A))
var/obj/O = A
jump_to = locate() in O
else if(isturf(A))
var/best_dist = INFINITY
for(var/obj/machinery/camera/camera in get_area(A))
if(!camera.can_use())
continue
if(!can_access_camera(camera, user))
continue
var/dist = get_dist(camera,A)
if(dist < best_dist)
best_dist = dist
jump_to = camera
if(isnull(jump_to))
return
if(can_access_camera(jump_to, user))
switch_to_camera(user, jump_to)
// Camera control: mouse.
/atom/DblClick()
..()
if(istype(usr.machine, /obj/machinery/computer/security))
var/obj/machinery/computer/security/console = usr.machine
console.jump_on_click(usr, src)
// Camera control: arrow keys.
/mob/Move(n, direct)
if(istype(machine, /obj/machinery/computer/security))
var/obj/machinery/computer/security/console = machine
var/turf/T = get_turf(console.watchers[src])
for(var/i; i < 10; i++)
T = get_step(T, direct)
console.jump_on_click(src, T)
return
return ..()
// Other computer monitors.
/obj/machinery/computer/security/telescreen
name = "telescreen"
+12 -266
View File
@@ -1,9 +1,4 @@
#define BUGMODE_LIST 0
#define BUGMODE_MONITOR 1
#define BUGMODE_TRACK 2
// This item just has an integrated camera console, which the data is "proxied" to
/obj/item/camera_bug
name = "camera bug"
desc = "For illicit snooping through the camera network."
@@ -14,270 +9,21 @@
throw_speed = 4
throw_range = 20
origin_tech = "syndicate=1;engineering=3"
/// Integrated camera console to serve UI data
var/obj/machinery/computer/security/integrated_console
var/obj/machinery/camera/current = null
var/last_net_update = 0
var/list/bugged_cameras = list()
var/track_mode = BUGMODE_LIST
var/last_tracked = 0
var/refresh_interval = 50
var/tracked_name = null
var/atom/tracking = null
var/last_found = null
var/last_seen = null
/obj/item/camera_bug/New()
..()
START_PROCESSING(SSobj, src)
/obj/item/camera_bug/Initialize(mapload)
. = ..()
integrated_console = new
integrated_console.parent = src
integrated_console.network = list("SS13")
/obj/item/camera_bug/Destroy()
get_cameras()
for(var/cam_tag in bugged_cameras)
var/obj/machinery/camera/camera = bugged_cameras[cam_tag]
if(camera.bug == src)
camera.bug = null
bugged_cameras = list()
if(tracking)
tracking = null
QDEL_NULL(integrated_console)
return ..()
/obj/item/camera_bug/interact(var/mob/user = usr)
var/datum/browser/popup = new(user, "camerabug","Camera Bug",nref=src)
popup.set_content(menu(get_cameras()))
popup.open()
/obj/item/camera_bug/attack_self(mob/user as mob)
user.set_machine(src)
interact(user)
tgui_interact(user)
/obj/item/camera_bug/check_eye(mob/user)
if(loc != user || user.incapacitated() || !user.has_vision() || !current)
user.unset_machine()
return FALSE
var/turf/T_user = get_turf(user.loc)
var/turf/T_current = get_turf(current)
if(!atoms_share_level(T_user, T_current) || !current.can_use())
to_chat(user, "<span class='danger'>[src] has lost the signal.</span>")
current = null
user.unset_machine()
return FALSE
return TRUE
/obj/item/camera_bug/on_unset_machine(mob/user)
user.reset_perspective(null)
/obj/item/camera_bug/proc/get_cameras()
if(world.time > (last_net_update + 100))
bugged_cameras = list()
for(var/obj/machinery/camera/camera in GLOB.cameranet.cameras)
if(camera.stat || !camera.can_use())
continue
if(length(list("SS13","MINE")&camera.network))
bugged_cameras[camera.c_tag] = camera
sortTim(bugged_cameras, /proc/cmp_text_asc)
return bugged_cameras
/obj/item/camera_bug/proc/menu(var/list/cameras)
if(!cameras || !cameras.len)
return "No bugged cameras found."
var/html
switch(track_mode)
if(BUGMODE_LIST)
html = "<h3>Select a camera:</h3> <a href='?src=[UID()];view'>\[Cancel camera view\]</a><hr><table>"
for(var/entry in cameras)
var/obj/machinery/camera/C = cameras[entry]
var/functions = ""
if(C.bug == src)
functions = " - <a href='?src=[UID()];monitor=\ref[C]'>\[Monitor\]</a> <a href='?src=[UID()];emp=\ref[C]'>\[Disable\]</a>"
else
functions = " - <a href='?src=[UID()];monitor=\ref[C]'>\[Monitor\]</a>"
html += "<tr><td><a href='?src=[UID()];view=\ref[C]'>[entry]</a></td><td>[functions]</td></tr>"
if(BUGMODE_MONITOR)
if(current)
html = "Analyzing Camera '[current.c_tag]' <a href='?src=[UID()];mode=0'>\[Select Camera\]</a><br>"
html += camera_report()
else
track_mode = BUGMODE_LIST
return .(cameras)
if(BUGMODE_TRACK)
if(tracking)
html = "Tracking '[tracked_name]' <a href='?src=[UID()];mode=0'>\[Cancel Tracking\]</a> <a href='?src=[UID()];view'>\[Cancel camera view\]</a><br>"
if(last_found)
var/time_diff = round((world.time - last_seen) / 150)
var/obj/machinery/camera/C = bugged_cameras[last_found]
var/outstring
if(C)
outstring = "<a href='?src=[UID()];view=\ref[C]'>[last_found]</a>"
else
outstring = last_found
if(!time_diff)
html += "Last seen near [outstring] (now)<br>"
else
// 15 second intervals ~ 1/4 minute
var/m = round(time_diff/4)
var/s = (time_diff - 4*m) * 15
if(!s) s = "00"
html += "Last seen near [outstring] ([m]:[s] minute\s ago)<br>"
if( C && (C.bug == src)) //Checks to see if the camera has a bug
html += "<a href='?src=[UID()];emp=\ref[C]'>\[Disable\]</a>"
else
html += "Not yet seen."
else
track_mode = BUGMODE_LIST
return .(cameras)
return html
/obj/item/camera_bug/proc/camera_report()
// this should only be called if current exists
var/dat = ""
if(current && current.can_use())
var/list/seen = current.can_see()
var/list/names = list()
for(var/obj/singularity/S in seen) // god help you if you see more than one
if(S.name in names)
names[S.name]++
dat += "[S.name] ([names[S.name]])"
else
names[S.name] = 1
dat += "[S.name]"
var/stage = round(S.current_size / 2)+1
dat += " (Stage [stage])"
dat += " <a href='?src=[UID()];track=\ref[S]'>\[Track\]</a><br>"
for(var/obj/mecha/M in seen)
if(M.name in names)
names[M.name]++
dat += "[M.name] ([names[M.name]])"
else
names[M.name] = 1
dat += "[M.name]"
dat += " <a href='?src=[UID()];track=\ref[M]'>\[Track\]</a><br>"
for(var/mob/living/M in seen)
if(M.name in names)
names[M.name]++
dat += "[M.name] ([names[M.name]])"
else
names[M.name] = 1
dat += "[M.name]"
if(M.buckled && !M.lying)
dat += " (Sitting)"
if(M.lying)
dat += " (Laying down)"
dat += " <a href='?src=[UID()];track=\ref[M]'>\[Track\]</a><br>"
if(length(dat) == 0)
dat += "No motion detected."
return dat
else
return "Camera Offline<br>"
/obj/item/camera_bug/Topic(var/href,var/list/href_list)
if(usr != loc)
usr.unset_machine()
usr << browse(null, "window=camerabug")
return
usr.set_machine(src)
if("mode" in href_list)
track_mode = text2num(href_list["mode"])
if("monitor" in href_list)
var/obj/machinery/camera/C = locate(href_list["monitor"])
if(C)
track_mode = BUGMODE_MONITOR
current = C
usr.reset_perspective(null)
interact()
if("track" in href_list)
var/atom/A = locate(href_list["track"])
if(A)
tracking = A
tracked_name = A.name
last_found = current.c_tag
last_seen = world.time
track_mode = BUGMODE_TRACK
if("emp" in href_list)
var/obj/machinery/camera/C = locate(href_list["emp"])
if(istype(C) && C.bug == src)
C.emp_act(1)
C.bug = null
bugged_cameras -= C.c_tag
interact()
return
if("close" in href_list)
usr.reset_perspective(null)
usr.unset_machine()
current = null
return // I do not <- I do not remember what I was going to write in this comment -Sayu, sometime later
if("view" in href_list)
var/obj/machinery/camera/C = locate(href_list["view"])
if(istype(C))
if(!C.can_use())
to_chat(usr, "<span class='danger'>Something's wrong with that camera. You can't get a feed.</span>")
return
var/turf/T = get_turf(loc)
if(!T || C.z != T.z)
to_chat(usr, "<span class='danger'>You can't get a signal.</span>")
return
current = C
spawn(6)
if(src.check_eye(usr))
usr.reset_perspective(C)
interact()
else
usr.unset_machine()
usr << browse(null, "window=camerabug")
return
else
usr.unset_machine()
usr.reset_perspective(null)
interact()
/obj/item/camera_bug/process()
if(track_mode == BUGMODE_LIST || (world.time < (last_tracked + refresh_interval)))
return
last_tracked = world.time
if(track_mode == BUGMODE_TRACK ) // search for user
// Note that it will be tricked if your name appears to change.
// This is not optimal but it is better than tracking you relentlessly despite everything.
if(!tracking)
src.updateSelfDialog()
return
if(tracking.name != tracked_name) // Hiding their identity, tricksy
var/mob/M = tracking
if(istype(M))
if(!(tracked_name == "Unknown" && findtext(tracking.name,"Unknown"))) // we saw then disguised before
if(!(tracked_name == M.real_name && findtext(tracking.name,M.real_name))) // or they're still ID'd
src.updateSelfDialog()//But if it's neither of those cases
return // you won't find em on the cameras
else
src.updateSelfDialog()
return
var/list/tracking_cams = list()
var/list/b_cams = get_cameras()
for(var/entry in b_cams)
tracking_cams += b_cams[entry]
var/list/target_region = view(tracking)
for(var/obj/machinery/camera/C in (target_region & tracking_cams))
if(!can_see(C,tracking)) // target may have xray, that doesn't make them visible to cameras
continue
if(C.can_use())
last_found = C.c_tag
last_seen = world.time
break
src.updateSelfDialog()
#undef BUGMODE_LIST
#undef BUGMODE_MONITOR
#undef BUGMODE_TRACK
/obj/item/camera_bug/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_inventory_state)
integrated_console.tgui_interact(user, ui_key, ui, force_open, master_ui, state)