mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 02:09:41 +00:00
TGUI Camera Console - Part 1; Plain console done
# Conflicts: # code/_onclick/hud/skybox.dm # code/controllers/subsystems/skybox.dm # code/game/machinery/computer/camera.dm # tgui/packages/tgui/public/tgui.bundle.js
This commit is contained in:
@@ -616,3 +616,24 @@ datum/projectile_data
|
||||
if (!client_or_usr)
|
||||
return
|
||||
winset(client_or_usr, "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))
|
||||
@@ -123,8 +123,3 @@
|
||||
|
||||
/obj/screen/fullscreen/fishbed
|
||||
icon_state = "fishbed"
|
||||
|
||||
#undef FULLSCREEN_LAYER
|
||||
#undef BLIND_LAYER
|
||||
#undef DAMAGE_LAYER
|
||||
#undef CRIT_LAYER
|
||||
171
code/_onclick/hud/map_popups.dm
Normal file
171
code/_onclick/hud/map_popups.dm
Normal file
@@ -0,0 +1,171 @@
|
||||
/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/register_map_obj.
|
||||
*/
|
||||
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
|
||||
icon_state = "blank"
|
||||
// Map view has to be on the lowest plane to enable proper lighting
|
||||
layer = SPACE_PLANE
|
||||
plane = SPACE_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 = SPACE_PLANE
|
||||
plane = SPACE_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")
|
||||
@@ -3,17 +3,20 @@
|
||||
#define SKYBOX_TURFS (SKYBOX_PIXELS/WORLD_ICON_SIZE)
|
||||
|
||||
// Skybox screen object.
|
||||
/obj/skybox
|
||||
/obj/screen/skybox
|
||||
name = "skybox"
|
||||
icon = null
|
||||
appearance_flags = TILE_BOUND|PIXEL_SCALE
|
||||
mouse_opacity = 0
|
||||
anchored = TRUE
|
||||
simulated = FALSE
|
||||
screen_loc = "CENTER,CENTER"
|
||||
layer = OBJ_LAYER
|
||||
plane = SKYBOX_PLANE
|
||||
blend_mode = BLEND_MULTIPLY // You actually need to do it this way or you see it in occlusion.
|
||||
|
||||
// Adjust transform property to scale for client's view var. We assume the skybox is 736x736 px
|
||||
/obj/skybox/proc/scale_to_view(var/view)
|
||||
/obj/screen/skybox/proc/scale_to_view(var/view)
|
||||
var/matrix/M = matrix()
|
||||
// Translate to center the icon over us!
|
||||
M.Translate(-(SKYBOX_PIXELS - WORLD_ICON_SIZE) / 2)
|
||||
@@ -23,7 +26,7 @@
|
||||
src.transform = M
|
||||
|
||||
/client
|
||||
var/obj/skybox/skybox
|
||||
var/obj/screen/skybox/skybox
|
||||
|
||||
/client/proc/update_skybox(rebuild)
|
||||
if(!skybox)
|
||||
|
||||
@@ -132,7 +132,7 @@ SUBSYSTEM_DEF(skybox)
|
||||
for(var/z in zlevels)
|
||||
skybox_cache["[z]"] = generate_skybox(z)
|
||||
|
||||
for(var/client/C)
|
||||
for(var/client/C in GLOB.clients)
|
||||
var/their_z = get_z(C.mob)
|
||||
if(!their_z) //Nullspace
|
||||
continue
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
var/global/datum/repository/cameras/camera_repository = new()
|
||||
|
||||
/proc/invalidateCameraCache()
|
||||
camera_repository.networks.Cut()
|
||||
camera_repository.invalidated = 1
|
||||
camera_repository.camera_cache_id = (++camera_repository.camera_cache_id % 999999)
|
||||
|
||||
/datum/repository/cameras
|
||||
var/list/networks
|
||||
var/invalidated = 1
|
||||
var/camera_cache_id = 1
|
||||
|
||||
/datum/repository/cameras/New()
|
||||
networks = list()
|
||||
..()
|
||||
|
||||
/datum/repository/cameras/proc/cameras_in_network(var/network, var/list/zlevels)
|
||||
setup_cache()
|
||||
var/list/network_list = networks[network]
|
||||
if(LAZYLEN(zlevels))
|
||||
var/list/filtered_cameras = list()
|
||||
for(var/list/C in network_list)
|
||||
//Camera is marked as always-visible
|
||||
if(C["omni"])
|
||||
filtered_cameras[++filtered_cameras.len] = C
|
||||
continue
|
||||
//Camera might be in an adjacent zlevel
|
||||
var/camz = C["z"]
|
||||
if(!camz) //It's inside something (helmet, communicator, etc) or nullspace or who knows
|
||||
camz = get_z(locate(C["camera"]) in cameranet.cameras)
|
||||
if(camz in zlevels)
|
||||
filtered_cameras[++filtered_cameras.len] = C //Can't add lists to lists with +=
|
||||
return filtered_cameras
|
||||
else
|
||||
return network_list
|
||||
|
||||
/datum/repository/cameras/proc/setup_cache()
|
||||
if(!invalidated)
|
||||
return
|
||||
invalidated = 0
|
||||
|
||||
cameranet.process_sort()
|
||||
for(var/obj/machinery/camera/C in cameranet.cameras)
|
||||
var/cam = C.nano_structure()
|
||||
for(var/network in C.network)
|
||||
if(!networks[network])
|
||||
networks[network] = list()
|
||||
var/list/netlist = networks[network]
|
||||
netlist[++netlist.len] = cam
|
||||
|
||||
@@ -232,12 +232,6 @@
|
||||
else
|
||||
to_chat(O, "<b><a href='byond://?src=\ref[O];track2=\ref[O];track=\ref[U];trackname=[U.name]'>[U]</a></b> holds \a [itemname] up to one of your cameras ...")
|
||||
O << browse(text("<HTML><HEAD><TITLE>[]</TITLE></HEAD><BODY><TT>[]</TT></BODY></HTML>", itemname, info), text("window=[]", itemname))
|
||||
for(var/mob/O in player_list)
|
||||
if (istype(O.machine, /obj/machinery/computer/security))
|
||||
var/obj/machinery/computer/security/S = O.machine
|
||||
if (S.current_camera == src)
|
||||
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(W, /obj/item/weapon/camera_bug))
|
||||
if (!src.can_use())
|
||||
@@ -494,8 +488,6 @@
|
||||
else
|
||||
cameranet.updateVisibility(src, 0)
|
||||
|
||||
invalidateCameraCache()
|
||||
|
||||
// Resets the camera's wires to fully operational state. Used by one of Malfunction abilities.
|
||||
/obj/machinery/camera/proc/reset_wires()
|
||||
if(!wires)
|
||||
|
||||
@@ -173,7 +173,6 @@ var/global/list/engineering_networks = list(
|
||||
var/number = my_area.len
|
||||
|
||||
c_tag = "[A.name] #[number]"
|
||||
invalidateCameraCache()
|
||||
|
||||
/obj/machinery/camera/autoname/Destroy()
|
||||
var/area/A = get_area(src)
|
||||
|
||||
@@ -3,201 +3,206 @@
|
||||
/obj/machinery/computer/security
|
||||
name = "security camera monitor"
|
||||
desc = "Used to access the various cameras on the station."
|
||||
|
||||
icon_keyboard = "security_key"
|
||||
icon_screen = "cameras"
|
||||
light_color = "#a91515"
|
||||
var/current_network = null
|
||||
var/obj/machinery/camera/current_camera = null
|
||||
var/last_pic = 1.0
|
||||
var/list/network
|
||||
var/mapping = 0//For the overview file, interesting bit of code.
|
||||
var/cache_id = 0
|
||||
circuit = /obj/item/weapon/circuitboard/security
|
||||
|
||||
/obj/machinery/computer/security/New()
|
||||
if(!network)
|
||||
var/mapping = 0//For the overview file, interesting bit of code.
|
||||
|
||||
var/list/network = list()
|
||||
var/obj/machinery/camera/active_camera
|
||||
var/list/concurrent_users = list()
|
||||
|
||||
// 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
|
||||
var/obj/screen/background/cam_foreground
|
||||
var/obj/screen/skybox/local_skybox
|
||||
|
||||
/obj/machinery/computer/security/Initialize()
|
||||
. = ..()
|
||||
if(!LAZYLEN(network))
|
||||
network = using_map.station_networks.Copy()
|
||||
..()
|
||||
if(network.len)
|
||||
current_network = network[1]
|
||||
map_name = "camera_console_[REF(src)]_map"
|
||||
// Initialize map objects
|
||||
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()
|
||||
|
||||
/obj/machinery/computer/security/attack_ai(var/mob/user as mob)
|
||||
return attack_hand(user)
|
||||
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
|
||||
|
||||
/obj/machinery/computer/security/check_eye(var/mob/user as mob)
|
||||
if (user.stat || ((get_dist(user, src) > 1 || !( user.canmove ) || user.blinded) && !istype(user, /mob/living/silicon))) //user can't see - not sure why canmove is here.
|
||||
return -1
|
||||
if(!current_camera)
|
||||
return 0
|
||||
var/viewflag = current_camera.check_eye(user)
|
||||
if ( viewflag < 0 ) //camera doesn't work
|
||||
reset_current()
|
||||
return viewflag
|
||||
local_skybox = new()
|
||||
local_skybox.assigned_map = map_name
|
||||
local_skybox.del_on_map_removal = FALSE
|
||||
local_skybox.screen_loc = "[map_name]:CENTER,CENTER"
|
||||
cam_plane_masters += local_skybox
|
||||
|
||||
/obj/machinery/computer/security/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1)
|
||||
if(stat & (NOPOWER|BROKEN)) return
|
||||
if(user.stat) return
|
||||
cam_background = new
|
||||
cam_background.assigned_map = map_name
|
||||
cam_background.del_on_map_removal = FALSE
|
||||
|
||||
var/data[0]
|
||||
var/mutable_appearance/scanlines = mutable_appearance('icons/effects/static.dmi', "scanlines")
|
||||
scanlines.alpha = 50
|
||||
scanlines.layer = FULLSCREEN_LAYER
|
||||
|
||||
data["current_camera"] = current_camera ? current_camera.nano_structure() : null
|
||||
data["current_network"] = current_network
|
||||
data["networks"] = network ? network : list()
|
||||
var/mutable_appearance/noise = mutable_appearance('icons/effects/static.dmi', "1 light")
|
||||
noise.layer = FULLSCREEN_LAYER
|
||||
|
||||
var/map_levels = using_map.get_map_levels(src.z, TRUE, om_range = DEFAULT_OVERMAP_RANGE)
|
||||
data["map_levels"] = map_levels
|
||||
cam_foreground = new
|
||||
cam_foreground.assigned_map = map_name
|
||||
cam_foreground.del_on_map_removal = FALSE
|
||||
cam_foreground.plane = PLANE_FULLSCREEN
|
||||
cam_foreground.add_overlay(scanlines)
|
||||
cam_foreground.add_overlay(noise)
|
||||
|
||||
if(current_network)
|
||||
data["cameras"] = camera_repository.cameras_in_network(current_network, map_levels)
|
||||
if(current_camera)
|
||||
switch_to_camera(user, current_camera)
|
||||
/obj/machinery/computer/security/Destroy()
|
||||
qdel(cam_screen)
|
||||
QDEL_LIST(cam_plane_masters)
|
||||
qdel(cam_background)
|
||||
qdel(cam_foreground)
|
||||
return ..()
|
||||
|
||||
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, 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")
|
||||
|
||||
ui.set_initial_data(data)
|
||||
/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_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
|
||||
// Turn on the console
|
||||
if(length(concurrent_users) == 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)
|
||||
user.client.register_map_obj(cam_foreground)
|
||||
// Open UI
|
||||
ui = new(user, src, ui_key, "CameraConsole", name, 870, 708, master_ui, state)
|
||||
ui.open()
|
||||
|
||||
/obj/machinery/computer/security/Topic(href, href_list)
|
||||
/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 1
|
||||
if(href_list["switch_camera"])
|
||||
if(stat&(NOPOWER|BROKEN)) return //VOREStation Edit - Removed zlevel check
|
||||
if(usr.stat || ((get_dist(usr, src) > 1 || !( usr.canmove ) || usr.blinded) && !istype(usr, /mob/living/silicon))) return
|
||||
var/obj/machinery/camera/C = locate(href_list["switch_camera"]) in cameranet.cameras
|
||||
if(!C)
|
||||
return
|
||||
if(!(current_network in C.network))
|
||||
return
|
||||
|
||||
switch_to_camera(usr, C)
|
||||
return 1
|
||||
else if(href_list["switch_network"])
|
||||
if(stat&(NOPOWER|BROKEN)) return //VOREStation Edit - Removed zlevel check
|
||||
if(usr.stat || ((get_dist(usr, src) > 1 || !( usr.canmove ) || usr.blinded) && !istype(usr, /mob/living/silicon))) return
|
||||
if(href_list["switch_network"] in network)
|
||||
current_network = href_list["switch_network"]
|
||||
return 1
|
||||
else if(href_list["reset"])
|
||||
if(stat&(NOPOWER|BROKEN)) return //VOREStation Edit - Removed zlevel check
|
||||
if(usr.stat || ((get_dist(usr, src) > 1 || !( usr.canmove ) || usr.blinded) && !istype(usr, /mob/living/silicon))) return
|
||||
reset_current()
|
||||
usr.reset_view(current_camera)
|
||||
return 1
|
||||
else
|
||||
. = ..()
|
||||
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)
|
||||
|
||||
/obj/machinery/computer/security/attack_hand(var/mob/user as mob)
|
||||
if(stat & (NOPOWER|BROKEN)) return
|
||||
// Show static if can't use the camera
|
||||
if(!active_camera?.can_use())
|
||||
show_camera_static()
|
||||
return TRUE
|
||||
|
||||
if(!isAI(user))
|
||||
user.set_machine(src)
|
||||
ui_interact(user)
|
||||
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
|
||||
|
||||
/obj/machinery/computer/security/proc/switch_to_camera(var/mob/user, var/obj/machinery/camera/C)
|
||||
//don't need to check if the camera works for AI because the AI jumps to the camera location and doesn't actually look through cameras.
|
||||
if(isAI(user))
|
||||
var/mob/living/silicon/ai/A = user
|
||||
// Only allow non-carded AIs to view because the interaction with the eye gets all wonky otherwise.
|
||||
if(!A.is_in_chassis())
|
||||
return 0
|
||||
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
|
||||
|
||||
A.eyeobj.setLoc(get_turf(C))
|
||||
A.client.eye = A.eyeobj
|
||||
return 1
|
||||
cam_screen.vis_contents = visible_turfs
|
||||
cam_background.icon_state = "clear"
|
||||
cam_background.fill_rect(1, 1, size_x, size_y)
|
||||
|
||||
if (!C.can_use() || user.stat || (get_dist(user, src) > 1 || user.machine != src || user.blinded || !( user.canmove ) && !istype(user, /mob/living/silicon)))
|
||||
return 0
|
||||
set_current(C)
|
||||
user.reset_view(current_camera)
|
||||
check_eye(user)
|
||||
return 1
|
||||
cam_foreground.fill_rect(1, 1, size_x, size_y)
|
||||
|
||||
/obj/machinery/computer/security/relaymove(mob/user,direct)
|
||||
var/turf/T = get_turf(current_camera)
|
||||
for(var/i; i < 10; i++)
|
||||
T = get_step(T, direct)
|
||||
jump_on_click(user, T)
|
||||
local_skybox.cut_overlays()
|
||||
local_skybox.add_overlay(SSskybox.get_skybox(get_z(C)))
|
||||
local_skybox.scale_to_view(size_x)
|
||||
local_skybox.set_position("CENTER", "CENTER", (world.maxx>>1) - C.x, (world.maxy>>1) - C.y)
|
||||
|
||||
//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))
|
||||
jump_to = locate() in A:head
|
||||
else if(isrobot(A))
|
||||
jump_to = A:camera
|
||||
else if(isobj(A))
|
||||
jump_to = locate() in A
|
||||
else if(isturf(A))
|
||||
var/best_dist = INFINITY
|
||||
for(var/obj/machinery/camera/camera in get_area(A))
|
||||
if(!camera.can_use())
|
||||
return TRUE
|
||||
|
||||
// Returns the list of cameras accessible from this computer
|
||||
/obj/machinery/computer/security/proc/get_available_cameras()
|
||||
if(z > 6) // Weird holdover from the old camera console code, not sure why this restriction was in place-- related to is_away_level not existing?
|
||||
return list()
|
||||
var/list/L = list()
|
||||
for(var/obj/machinery/camera/C in 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(!can_access_camera(camera))
|
||||
if(!(islist(C.network)))
|
||||
stack_trace("Camera in a cameranet has a non-list camera network")
|
||||
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))
|
||||
switch_to_camera(user,jump_to)
|
||||
var/list/tempnetwork = C.network & network
|
||||
if(tempnetwork.len)
|
||||
D["[C.c_tag]"] = C
|
||||
return D
|
||||
|
||||
/obj/machinery/computer/security/process()
|
||||
if(cache_id != camera_repository.camera_cache_id)
|
||||
cache_id = camera_repository.camera_cache_id
|
||||
SSnanoui.update_uis(src)
|
||||
|
||||
/obj/machinery/computer/security/proc/can_access_camera(var/obj/machinery/camera/C)
|
||||
var/list/shared_networks = src.network & C.network
|
||||
if(shared_networks.len)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/obj/machinery/computer/security/proc/set_current(var/obj/machinery/camera/C)
|
||||
if(current_camera == C)
|
||||
/obj/machinery/computer/security/attack_hand(mob/user)
|
||||
if(stat || ..())
|
||||
user.unset_machine()
|
||||
return
|
||||
|
||||
if(current_camera)
|
||||
reset_current()
|
||||
tgui_interact(user)
|
||||
|
||||
src.current_camera = C
|
||||
if(current_camera)
|
||||
current_camera.camera_computers_using_this.Add(src)
|
||||
update_use_power(USE_POWER_ACTIVE)
|
||||
var/mob/living/L = current_camera.loc
|
||||
if(istype(L))
|
||||
L.tracking_initiated()
|
||||
/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/reset_current()
|
||||
if(current_camera)
|
||||
current_camera.camera_computers_using_this.Remove(src)
|
||||
var/mob/living/L = current_camera.loc
|
||||
if(istype(L))
|
||||
L.tracking_cancelled()
|
||||
current_camera = null
|
||||
update_use_power(USE_POWER_IDLE)
|
||||
|
||||
//Camera control: mouse.
|
||||
/* Oh my god
|
||||
/atom/DblClick()
|
||||
..()
|
||||
if(istype(usr.machine,/obj/machinery/computer/security))
|
||||
var/obj/machinery/computer/security/console = usr.machine
|
||||
console.jump_on_click(usr,src)
|
||||
*/
|
||||
/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)
|
||||
local_skybox.cut_overlays()
|
||||
|
||||
//Camera control: arrow keys.
|
||||
/obj/machinery/computer/security/telescreen
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
var/list/sources = new() //List of sources triggering the alarm. Used to determine when the alarm should be cleared.
|
||||
var/list/sources_assoc = new() //Associative list of source triggers. Used to efficiently acquire the alarm source.
|
||||
var/list/cameras //List of cameras that can be switched to, if the player has that capability.
|
||||
var/cache_id //ID for camera cache, changed by invalidateCameraCache().
|
||||
var/area/last_area //The last acquired area, used should origin be lost (for example a destroyed borg containing an alarming camera).
|
||||
var/area/last_name //The last acquired name, used should origin be lost
|
||||
var/area/last_camera_area //The last area in which cameras where fetched, used to see if the camera list should be updated.
|
||||
@@ -78,15 +77,10 @@
|
||||
return last_name
|
||||
|
||||
/datum/alarm/proc/cameras()
|
||||
// reset camera cache
|
||||
if(camera_repository.camera_cache_id != cache_id)
|
||||
cameras = null
|
||||
cache_id = camera_repository.camera_cache_id
|
||||
// If the alarm origin has changed area, for example a borg containing an alarming camera, reset the list of cameras
|
||||
else if(cameras && (last_camera_area != alarm_area()))
|
||||
if(cameras && (last_camera_area != alarm_area()))
|
||||
cameras = null
|
||||
|
||||
// The list of cameras is also reset by /proc/invalidateCameraCache()
|
||||
if(!cameras)
|
||||
cameras = origin ? origin.get_alarm_cameras() : last_area.get_alarm_cameras()
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
|
||||
/obj/machinery/camera/deactivate(user as mob, var/choice = 1)
|
||||
..(user, choice)
|
||||
invalidateCameraCache()
|
||||
if(src.can_use())
|
||||
cameranet.addCamera(src)
|
||||
else
|
||||
|
||||
@@ -68,10 +68,10 @@
|
||||
|
||||
data["networks"] = all_networks
|
||||
|
||||
var/list/map_levels = using_map.get_map_levels(get_z(nano_host()), TRUE, om_range = DEFAULT_OVERMAP_RANGE)
|
||||
// var/list/map_levels = using_map.get_map_levels(get_z(nano_host()), TRUE, om_range = DEFAULT_OVERMAP_RANGE)
|
||||
|
||||
if(current_network)
|
||||
data["cameras"] = camera_repository.cameras_in_network(current_network, map_levels)
|
||||
// if(current_network) // TODO: Fix
|
||||
// data["cameras"] = camera_repository.cameras_in_network(current_network, map_levels)
|
||||
|
||||
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
|
||||
if (!ui)
|
||||
|
||||
BIN
icons/mob/map_backgrounds.dmi
Normal file
BIN
icons/mob/map_backgrounds.dmi
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 555 B |
BIN
sound/machines/terminal_on.ogg
Normal file
BIN
sound/machines/terminal_on.ogg
Normal file
Binary file not shown.
135
tgui/packages/tgui/interfaces/CameraConsole.js
Normal file
135
tgui/packages/tgui/interfaces/CameraConsole.js
Normal file
@@ -0,0 +1,135 @@
|
||||
import { filter, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { classes } from 'common/react';
|
||||
import { createSearch } from 'common/string';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Button, ByondUi, Input, Section } from '../components';
|
||||
import { refocusLayout, Window } from '../layouts';
|
||||
|
||||
/**
|
||||
* Returns previous and next camera names relative to the currently
|
||||
* active camera.
|
||||
*/
|
||||
const prevNextCamera = (cameras, activeCamera) => {
|
||||
if (!activeCamera) {
|
||||
return [];
|
||||
}
|
||||
const index = cameras.findIndex(camera => (
|
||||
camera.name === activeCamera.name
|
||||
));
|
||||
return [
|
||||
cameras[index - 1]?.name,
|
||||
cameras[index + 1]?.name,
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Camera selector.
|
||||
*
|
||||
* Filters cameras, applies search terms and sorts the alphabetically.
|
||||
*/
|
||||
const selectCameras = (cameras, searchText = '') => {
|
||||
const testSearch = createSearch(searchText, camera => camera.name);
|
||||
return flow([
|
||||
// Null camera filter
|
||||
filter(camera => camera?.name),
|
||||
// Optional search term
|
||||
searchText && filter(testSearch),
|
||||
// Slightly expensive, but way better than sorting in BYOND
|
||||
sortBy(camera => camera.name),
|
||||
])(cameras);
|
||||
};
|
||||
|
||||
export const CameraConsole = (props, context) => {
|
||||
const { act, data, config } = useBackend(context);
|
||||
const { mapRef, activeCamera } = data;
|
||||
const cameras = selectCameras(data.cameras);
|
||||
const [
|
||||
prevCameraName,
|
||||
nextCameraName,
|
||||
] = prevNextCamera(cameras, activeCamera);
|
||||
return (
|
||||
<Window resizable>
|
||||
<div className="CameraConsole__left">
|
||||
<Window.Content scrollable>
|
||||
<CameraConsoleContent />
|
||||
</Window.Content>
|
||||
</div>
|
||||
<div className="CameraConsole__right">
|
||||
<div className="CameraConsole__toolbar">
|
||||
<b>Camera: </b>
|
||||
{activeCamera
|
||||
&& activeCamera.name
|
||||
|| '—'}
|
||||
</div>
|
||||
<div className="CameraConsole__toolbarRight">
|
||||
<Button
|
||||
icon="chevron-left"
|
||||
disabled={!prevCameraName}
|
||||
onClick={() => act('switch_camera', {
|
||||
name: prevCameraName,
|
||||
})} />
|
||||
<Button
|
||||
icon="chevron-right"
|
||||
disabled={!nextCameraName}
|
||||
onClick={() => act('switch_camera', {
|
||||
name: nextCameraName,
|
||||
})} />
|
||||
</div>
|
||||
<ByondUi
|
||||
className="CameraConsole__map"
|
||||
params={{
|
||||
id: mapRef,
|
||||
parent: config.window,
|
||||
type: 'map',
|
||||
}} />
|
||||
</div>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
export const CameraConsoleContent = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const [
|
||||
searchText,
|
||||
setSearchText,
|
||||
] = useLocalState(context, 'searchText', '');
|
||||
const { activeCamera } = data;
|
||||
const cameras = selectCameras(data.cameras, searchText);
|
||||
return (
|
||||
<Fragment>
|
||||
<Input
|
||||
fluid
|
||||
mb={1}
|
||||
placeholder="Search for a camera"
|
||||
onInput={(e, value) => setSearchText(value)} />
|
||||
<Section>
|
||||
{cameras.map(camera => (
|
||||
// We're not using the component here because performance
|
||||
// would be absolutely abysmal (50+ ms for each re-render).
|
||||
<div
|
||||
key={camera.name}
|
||||
title={camera.name}
|
||||
className={classes([
|
||||
'Button',
|
||||
'Button--fluid',
|
||||
'Button--color--transparent',
|
||||
'Button--ellipsis',
|
||||
activeCamera
|
||||
&& camera.name === activeCamera.name
|
||||
&& 'Button--selected',
|
||||
])}
|
||||
onClick={() => {
|
||||
refocusLayout();
|
||||
act('switch_camera', {
|
||||
name: camera.name,
|
||||
});
|
||||
}}>
|
||||
{camera.name}
|
||||
</div>
|
||||
))}
|
||||
</Section>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
@@ -156,6 +156,7 @@
|
||||
#include "code\_onclick\hud\gun_mode.dm"
|
||||
#include "code\_onclick\hud\hud.dm"
|
||||
#include "code\_onclick\hud\human.dm"
|
||||
#include "code\_onclick\hud\map_popups.dm"
|
||||
#include "code\_onclick\hud\movable_screen_objects.dm"
|
||||
#include "code\_onclick\hud\other_mobs.dm"
|
||||
#include "code\_onclick\hud\picture_in_picture.dm"
|
||||
|
||||
Reference in New Issue
Block a user