mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-25 21:19:00 +01:00
[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:
@@ -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))
|
||||
|
||||
@@ -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")
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user