From d8450b493351733fb47af8fbee3e6765f64bbeb2 Mon Sep 17 00:00:00 2001 From: tonty <39193182+tontyGH@users.noreply.github.com> Date: Thu, 21 Nov 2024 06:55:38 -0500 Subject: [PATCH] Camera eyes have been lightly refactored (among other things...) (#87805) ## About The Pull Request * A generic /mob/eye/camera type has been made, containing everything needed to interface with a cameranet * /mob/eye/ai_eye has been refactored into a generic /mob/eye/camera instance * Advanced cameras no longer inherit from AI eyes, splitting off behaviour * Camera code has been somewhat cleaned up * Probably some more stuff I'm forgetting right now ## Big man Southport: ![image](https://github.com/user-attachments/assets/de6e6ff0-ca99-4636-bdec-5e0b2d6b8037) ## Changelog :cl: code: made /proc/getviewsize() pure refactor: mob/eye/ai_eye has been restructured, now inheriting from a generic mob/eye/camera type refactor: advanced cameras and their subtypes are now mob/eye/camera/remote subtypes code: the cameranet no longer expects the user to be an AI eye code: remote camera eyes have had their initialization streamlined code: remote cameras handle assigning and unassigning users by themselves now code: remote cameras now use weakrefs instead of hard referencing owners and origins code: also the sentient disease is_define was removed (we don't have those anymore) fix: AI eyes no longer assign real names to themselves, fixing their orbit name /:cl: --------- Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- .github/guides/ISSUE_MANAGER.md | 4 +- code/__DEFINES/is_helpers.dm | 22 ++- code/__HELPERS/view.dm | 6 +- code/_globalvars/lists/mobs.dm | 2 +- .../plane_masters/plane_master_subtypes.dm | 2 +- code/datums/components/supermatter_crystal.dm | 2 +- code/datums/holocall.dm | 26 +-- code/game/atom/_atom.dm | 2 +- code/game/data_huds.dm | 2 +- .../machinery/computer/camera_advanced.dm | 176 ++++++------------ code/game/objects/items/devices/multitool.dm | 4 +- .../construction_actions.dm | 2 +- .../construction_console.dm | 13 +- .../antagonists/abductor/machinery/camera.dm | 13 +- code/modules/forensics/_forensics.dm | 4 +- code/modules/mob/eye/camera/camera.dm | 80 ++++++++ code/modules/mob/eye/camera/remote.dm | 130 +++++++++++++ code/modules/mob/eye/eye.dm | 6 +- code/modules/mob/living/silicon/ai/ai.dm | 6 +- .../living/silicon/ai/freelook/cameranet.dm | 13 +- .../mob/living/silicon/ai/freelook/chunk.dm | 31 +-- .../mob/living/silicon/ai/freelook/eye.dm | 146 ++++++--------- .../modules/mob/living/silicon/ai/multicam.dm | 33 ++-- .../research/xenobiology/xenobio_camera.dm | 58 +++--- code/modules/shuttle/navigation_computer.dm | 43 ++--- code/modules/unit_tests/mob_faction.dm | 4 +- code/modules/unit_tests/unit_test.dm | 4 +- .../vehicles/mecha/mecha_ai_interaction.dm | 2 +- tgstation.dme | 2 + 29 files changed, 477 insertions(+), 361 deletions(-) create mode 100644 code/modules/mob/eye/camera/camera.dm create mode 100644 code/modules/mob/eye/camera/remote.dm diff --git a/.github/guides/ISSUE_MANAGER.md b/.github/guides/ISSUE_MANAGER.md index 3ecab32a7f5..9e5745d7731 100644 --- a/.github/guides/ISSUE_MANAGER.md +++ b/.github/guides/ISSUE_MANAGER.md @@ -28,8 +28,8 @@ If an issue reports a runtime, it must have the actual runtime call stack provid - usr.loc: the floor (150,25,4) (/turf/open/floor/circuit) - call stack: - Camera Net (/datum/cameranet): visibility(/list (/list), null, /list (/list), 1) - - AI (/mob/living/silicon/ai): camera visibility(Inactive AI Eye (/mob/eye/ai_eye)) - - Inactive AI Eye (/mob/eye/ai_eye): setLoc(the floor (150,25,4) (/turf/open/floor/circuit), 0) + - AI (/mob/living/silicon/ai): camera visibility(Inactive AI Eye (/mob/eye/camera/ai)) + - Inactive AI Eye (/mob/eye/camera/ai): setLoc(the floor (150,25,4) (/turf/open/floor/circuit), 0) - AI (/mob/living/silicon/ai): create eye() - AI (/mob/living/silicon/ai): Initialize(0, null, TagGamerGame2 (/mob/dead/new_player)) - Atoms (/datum/controller/subsystem/atoms): InitAtom(AI (/mob/living/silicon/ai), 0, /list (/list)) diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index b7919353f1a..8548c284ced 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -202,21 +202,23 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( #define isspider(A) (istype(A, /mob/living/basic/spider)) - -//Misc mobs -#define isobserver(A) (istype(A, /mob/dead/observer)) - -#define isdead(A) (istype(A, /mob/dead)) - -#define isnewplayer(A) (istype(A, /mob/dead/new_player)) +//Eye mobs +#define iseyemob(A) (istype(A, /mob/eye)) #define isovermind(A) (istype(A, /mob/eye/blob)) -#define issentientdisease(A) (istype(A, /mob/eye/disease)) +#define iscameramob(A) (istype(A, /mob/eye/camera)) -#define iseyemob(A) (istype(A, /mob/eye)) +#define isaicamera(A) (istype(A, /mob/eye/camera/ai)) -#define isaicamera(A) (istype(A, /mob/eye/ai_eye)) +#define isremotecamera(A) (istype(A, /mob/eye/camera/remote)) + +//Dead mobs +#define isdead(A) (istype(A, /mob/dead)) + +#define isobserver(A) (istype(A, /mob/dead/observer)) + +#define isnewplayer(A) (istype(A, /mob/dead/new_player)) //Objects #define isobj(A) istype(A, /obj) //override the byond proc because it returns true on children of /atom/movable that aren't objs diff --git a/code/__HELPERS/view.dm b/code/__HELPERS/view.dm index 139bdedc425..61aaed88350 100644 --- a/code/__HELPERS/view.dm +++ b/code/__HELPERS/view.dm @@ -1,7 +1,5 @@ -/proc/getviewsize(view) - if(!view) // Just to avoid any runtimes that could otherwise cause constant disconnect loops. - stack_trace("Missing value for 'view' in getviewsize(), defaulting to world.view!") - view = world.view +/proc/getviewsize(view = world.view) + SHOULD_BE_PURE(TRUE) if(isnum(view)) var/totalviewrange = (view < 0 ? -1 : 1) + 2 * view diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index d91eec6e1bc..5ece846e84d 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -59,7 +59,7 @@ GLOBAL_LIST_EMPTY(available_ai_shells) GLOBAL_LIST_INIT(simple_animals, list(list(),list(),list())) // One for each AI_* status define GLOBAL_LIST_EMPTY(spidermobs) //all sentient spider mobs GLOBAL_LIST_EMPTY(bots_list) -GLOBAL_LIST_EMPTY(aiEyes) +GLOBAL_LIST_EMPTY(camera_eyes) GLOBAL_LIST_EMPTY(suit_sensors_list) //all people with suit sensors on /// All alive mobs with clients. diff --git a/code/_onclick/hud/rendering/plane_masters/plane_master_subtypes.dm b/code/_onclick/hud/rendering/plane_masters/plane_master_subtypes.dm index acfa5ee274c..582253e0b92 100644 --- a/code/_onclick/hud/rendering/plane_masters/plane_master_subtypes.dm +++ b/code/_onclick/hud/rendering/plane_masters/plane_master_subtypes.dm @@ -362,7 +362,7 @@ /atom/movable/screen/plane_master/camera_static/proc/eye_changed(datum/hud/source, atom/old_eye, atom/new_eye) SIGNAL_HANDLER - if(!isaicamera(new_eye)) + if(!iscameramob(new_eye)) if(!force_hidden) hide_plane(source.mymob) return diff --git a/code/datums/components/supermatter_crystal.dm b/code/datums/components/supermatter_crystal.dm index e8e6f00ed2f..14183e4beb6 100644 --- a/code/datums/components/supermatter_crystal.dm +++ b/code/datums/components/supermatter_crystal.dm @@ -107,7 +107,7 @@ if(iscyborg(user) && atom_source.Adjacent(user)) dust_mob(source, user, cause = "cyborg attack") return - if(isaicamera(user)) + if(iscameramob(user)) return if(islarva(user)) dust_mob(source, user, cause = "larva attack") diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm index 84889ad6bd9..ceaa53e0455 100644 --- a/code/datums/holocall.dm +++ b/code/datums/holocall.dm @@ -1,15 +1,16 @@ -/mob/eye/ai_eye/remote/holo/setLoc(turf/destination, force_update = FALSE) +/mob/eye/camera/remote/holo/setLoc(turf/destination, force_update = FALSE) // If we're moving outside the space of our projector, then just... don't - var/obj/machinery/holopad/H = origin - if(!H?.move_hologram(eye_user, destination)) + var/obj/machinery/holopad/H = origin_ref?.resolve() + if(!H?.move_hologram(user_ref?.resolve(), destination)) sprint = initial(sprint) // Reset sprint so it doesn't balloon in our calling proc return return ..() /obj/machinery/holopad/remove_eye_control(mob/living/user) - if(user.client) - user.reset_perspective(null) - user.remote_control = null + var/mob/eye/camera/remote/eye = user.remote_control + if(!istype(eye)) + CRASH("Attempted to remove eye control from non-camera eye. Something has gone horribly wrong.") + eye.assign_user(null) //this datum manages its own references @@ -24,7 +25,7 @@ var/list/dialed_holopads ///user's eye, once connected - var/mob/eye/ai_eye/remote/holo/eye + var/mob/eye/camera/remote/holo/eye ///user's hologram, once connected var/obj/effect/overlay/holo_pad_hologram/hologram ///hangup action @@ -155,15 +156,8 @@ hologram = answering_holopad.activate_holo(user) hologram.HC = src - //eyeobj code is horrid, this is the best copypasta I could make - eye = new - eye.origin = answering_holopad - eye.eye_initialized = TRUE - eye.eye_user = user - eye.name = "Camera Eye ([user.name])" - user.remote_control = eye - user.reset_perspective(eye) - eye.setLoc(answering_holopad.loc) + eye = new(get_turf(answering_holopad), answering_holopad) + eye.assign_user(user) hangup = new(eye, src) hangup.Grant(user) diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm index 19dbc2de330..b6c5b3959ca 100644 --- a/code/game/atom/_atom.dm +++ b/code/game/atom/_atom.dm @@ -881,7 +881,7 @@ var/extra_context = "" var/used_name = name - if(isliving(user) || isovermind(user) || isaicamera(user) || (ghost_screentips && isobserver(user))) + if(isliving(user) || isovermind(user) || iscameramob(user) || (ghost_screentips && isobserver(user))) var/obj/item/held_item = user.get_active_held_item() if (user.mob_flags & MOB_HAS_SCREENTIPS_NAME_OVERRIDE) diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm index faf1c896946..cbaf94e6583 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -73,7 +73,7 @@ . = ..() if(!new_viewer || hud_users_all_z_levels.len != 1) return - for(var/mob/eye/ai_eye/eye as anything in GLOB.aiEyes) + for(var/mob/eye/camera/ai/eye as anything in GLOB.camera_eyes) eye.update_ai_detect_hud() /datum/atom_hud/data/malf_apc diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm index 80a06fa6b9f..5633fe49047 100644 --- a/code/game/machinery/computer/camera_advanced.dm +++ b/code/game/machinery/computer/camera_advanced.dm @@ -8,7 +8,7 @@ var/list/z_lock = list() // Lock use to these z levels var/lock_override = NONE - var/mob/eye/ai_eye/remote/eyeobj + var/mob/eye/camera/remote/eyeobj var/mob/living/current_user = null var/list/networks = list(CAMERANET_NETWORK_SS13) /// Typepath of the action button we use as "off" @@ -76,27 +76,33 @@ /obj/machinery/computer/camera_advanced/syndie/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock) return //For syndie nuke shuttle, to spy for station. +/** + * Initializes a camera eye. + * Returns TRUE if initialization was successful. + * Will return nothing if it runtimes. + */ /obj/machinery/computer/camera_advanced/proc/CreateEye() - eyeobj = new() - eyeobj.origin = src + if(eyeobj) + CRASH("Tried to make another eyeobj for some reason. Why?") + + eyeobj = new(get_turf(src), src) + return TRUE /obj/machinery/computer/camera_advanced/proc/GrantActions(mob/living/user) for(var/datum/action/to_grant as anything in actions) to_grant.Grant(user) /obj/machinery/proc/remove_eye_control(mob/living/user) - CRASH("[type] does not implement ai eye handling") + CRASH("[type] does not implement camera eye handling") /obj/machinery/computer/camera_advanced/proc/give_eye_control(mob/user) if(isnull(user?.client)) return - GrantActions(user) + current_user = user - eyeobj.eye_user = user - eyeobj.name = "Camera Eye ([user.name])" - user.remote_control = eyeobj - user.reset_perspective(eyeobj) - eyeobj.setLoc(eyeobj.loc) + eyeobj.assign_user(user) + GrantActions(user) + if(should_supress_view_changes) user.client.view_size.supress() begin_processing() @@ -110,14 +116,11 @@ for(var/datum/camerachunk/camerachunks_gone as anything in eyeobj.visibleCameraChunks) camerachunks_gone.remove(eyeobj) - user.reset_perspective(null) - if(eyeobj.visible_icon) - user.client.images -= eyeobj.user_image + eyeobj.assign_user(null) + current_user = null + user.client.view_size.unsupress() - eyeobj.eye_user = null - user.remote_control = null - current_user = null playsound(src, 'sound/machines/terminal/terminal_off.ogg', 25, FALSE) /obj/machinery/computer/camera_advanced/on_set_is_operational(old_value) @@ -148,39 +151,43 @@ if(!QDELETED(current_user)) to_chat(user, span_warning("The console is already in use!")) return - var/mob/living/L = user - if(!eyeobj) - CreateEye() - if(!eyeobj) //Eye creation failed + + if(eyeobj) + give_eye_control(user) + eyeobj.setLoc(eyeobj.loc) return - if(!eyeobj.eye_initialized) - var/camera_location - var/turf/myturf = get_turf(src) - if(eyeobj.use_static != FALSE) - if((!length(z_lock) || (myturf.z in z_lock)) && GLOB.cameranet.checkTurfVis(myturf)) - camera_location = myturf - else - for(var/obj/machinery/camera/C as anything in GLOB.cameranet.cameras) - if(!C.can_use() || length(z_lock) && !(C.z in z_lock)) - continue - var/list/network_overlap = networks & C.network - if(length(network_overlap)) - camera_location = get_turf(C) - break + /* We're attempting to initialize the eye past this point */ + + if(!CreateEye()) + to_chat(user, span_warning("\The [src] flashes a bunch of never-ending errors on the display. Something is really wrong.")) + return + + var/camera_location + var/turf/myturf = get_turf(src) + var/consider_zlock = (!!length(z_lock)) + + if(!eyeobj.use_visibility) + if(consider_zlock && !(myturf.z in z_lock)) + camera_location = locate(round(world.maxx * 0.5), round(world.maxy * 0.5), z_lock[1]) else camera_location = myturf - if(length(z_lock) && !(myturf.z in z_lock)) - camera_location = locate(round(world.maxx/2), round(world.maxy/2), z_lock[1]) - - if(camera_location) - eyeobj.eye_initialized = TRUE - give_eye_control(L) - eyeobj.setLoc(camera_location) - else - unset_machine() else - give_eye_control(L) - eyeobj.setLoc(eyeobj.loc) + if((!consider_zlock || (myturf.z in z_lock)) && GLOB.cameranet.checkTurfVis(myturf)) + camera_location = myturf + else + for(var/obj/machinery/camera/C as anything in GLOB.cameranet.cameras) + if(!C.can_use() || consider_zlock && !(C.z in z_lock)) + continue + var/list/network_overlap = networks & C.network + if(length(network_overlap)) + camera_location = get_turf(C) + break + + if(camera_location) + give_eye_control(user) + eyeobj.setLoc(camera_location, TRUE) + else + unset_machine() /obj/machinery/computer/camera_advanced/attack_robot(mob/user) return attack_hand(user) @@ -188,73 +195,6 @@ /obj/machinery/computer/camera_advanced/attack_ai(mob/user) return //AIs would need to disable their own camera procs to use the console safely. Bugs happen otherwise. -/mob/eye/ai_eye/remote - name = "Inactive Camera Eye" - ai_detector_visible = FALSE - var/sprint = 10 - var/cooldown = 0 - var/acceleration = 1 - var/mob/living/eye_user = null - var/obj/machinery/origin - var/eye_initialized = 0 - var/visible_icon = 0 - var/image/user_image = null - -/mob/eye/ai_eye/remote/update_remote_sight(mob/living/user) - user.set_invis_see(SEE_INVISIBLE_LIVING) //can't see ghosts through cameras - user.set_sight(SEE_TURFS) - return TRUE - -/mob/eye/ai_eye/remote/Destroy() - if(origin && eye_user) - origin.remove_eye_control(eye_user,src) - origin = null - . = ..() - eye_user = null - -/mob/eye/ai_eye/remote/GetViewerClient() - if(eye_user) - return eye_user.client - return null - -/mob/eye/ai_eye/remote/setLoc(turf/destination, force_update = FALSE) - if(eye_user) - destination = get_turf(destination) - if (destination) - abstract_move(destination) - else - moveToNullspace() - - update_ai_detect_hud() - - if(use_static) - GLOB.cameranet.visibility(src, GetViewerClient(), null, use_static) - - if(visible_icon) - if(eye_user.client) - eye_user.client.images -= user_image - user_image = image(icon,loc,icon_state, FLY_LAYER) - SET_PLANE(user_image, ABOVE_GAME_PLANE, destination) - eye_user.client.images += user_image - -/mob/eye/ai_eye/remote/relaymove(mob/living/user, direction) - var/initial = initial(sprint) - var/max_sprint = 50 - - if(cooldown && cooldown < world.timeofday) // 3 seconds - sprint = initial - - for(var/i = 0; i < max(sprint, initial); i += 20) - var/turf/step = get_turf(get_step(src, direction)) - if(step) - setLoc(step) - - cooldown = world.timeofday + 5 - if(acceleration) - sprint = min(sprint + 0.5, max_sprint) - else - sprint = initial - /datum/action/innate/camera_off name = "End Camera View" button_icon = 'icons/mob/actions/actions_silicon.dmi' @@ -263,8 +203,8 @@ /datum/action/innate/camera_off/Activate() if(!owner || !isliving(owner)) return - var/mob/eye/ai_eye/remote/remote_eye = owner.remote_control - var/obj/machinery/computer/camera_advanced/console = remote_eye.origin + var/mob/eye/camera/remote/remote_eye = owner.remote_control + var/obj/machinery/computer/camera_advanced/console = remote_eye.origin_ref.resolve() console.remove_eye_control(owner) /datum/action/innate/camera_jump @@ -275,8 +215,8 @@ /datum/action/innate/camera_jump/Activate() if(!owner || !isliving(owner)) return - var/mob/eye/ai_eye/remote/remote_eye = owner.remote_control - var/obj/machinery/computer/camera_advanced/origin = remote_eye.origin + var/mob/eye/camera/remote/remote_eye = owner.remote_control + var/obj/machinery/computer/camera_advanced/origin = remote_eye.origin_ref.resolve() var/list/L = list() @@ -320,7 +260,7 @@ /datum/action/innate/camera_multiz_up/Activate() if(!owner || !isliving(owner)) return - var/mob/eye/ai_eye/remote/remote_eye = owner.remote_control + var/mob/eye/camera/remote/remote_eye = owner.remote_control if(remote_eye.zMove(UP)) to_chat(owner, span_notice("You move upwards.")) else @@ -334,7 +274,7 @@ /datum/action/innate/camera_multiz_down/Activate() if(!owner || !isliving(owner)) return - var/mob/eye/ai_eye/remote/remote_eye = owner.remote_control + var/mob/eye/camera/remote/remote_eye = owner.remote_control if(remote_eye.zMove(DOWN)) to_chat(owner, span_notice("You move downwards.")) else diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm index e323c65b1eb..0ea1e867b4c 100644 --- a/code/game/objects/items/devices/multitool.dm +++ b/code/game/objects/items/devices/multitool.dm @@ -204,7 +204,7 @@ var/turf/our_turf = get_turf(src) detect_state = PROXIMITY_NONE - for(var/mob/eye/ai_eye/AI_eye as anything in GLOB.aiEyes) + for(var/mob/eye/camera/ai/AI_eye as anything in GLOB.camera_eyes) if(!AI_eye.ai_detector_visible) continue @@ -253,7 +253,7 @@ // copied from camera chunks but we are doing a really big edge case here though /obj/item/multitool/ai_detect/proc/surrounding_chunks(turf/epicenter) . = list() - var/static_range = /mob/eye/ai_eye::static_visibility_range + var/static_range = /mob/eye/camera/ai::static_visibility_range var/x1 = max(1, epicenter.x - static_range) var/y1 = max(1, epicenter.y - static_range) var/x2 = min(world.maxx, epicenter.x + static_range) diff --git a/code/game/objects/structures/construction_console/construction_actions.dm b/code/game/objects/structures/construction_console/construction_actions.dm index b3a4e309ffa..cce7d2ee516 100644 --- a/code/game/objects/structures/construction_console/construction_actions.dm +++ b/code/game/objects/structures/construction_console/construction_actions.dm @@ -5,7 +5,7 @@ /datum/action/innate/construction button_icon = 'icons/mob/actions/actions_construction.dmi' ///Console's eye mob - var/mob/eye/ai_eye/remote/base_construction/remote_eye + var/mob/eye/camera/remote/base_construction/remote_eye ///Console itself var/obj/machinery/computer/camera_advanced/base_construction/base_console ///Is this used to build only on the station z level? diff --git a/code/game/objects/structures/construction_console/construction_console.dm b/code/game/objects/structures/construction_console/construction_console.dm index 97b83acccae..69275d0e951 100644 --- a/code/game/objects/structures/construction_console/construction_console.dm +++ b/code/game/objects/structures/construction_console/construction_console.dm @@ -1,7 +1,7 @@ /** * Camera console used to control a base building drone * - * Using this console will put the user in control of a [base building drone][/mob/eye/ai_eye/remote/base_construction]. + * Using this console will put the user in control of a [base building drone][/mob/eye/camera/remote/base_construction]. * The drone will appear somewhere within the allowed_area var, or if no area is specified, at the location of the console.area * Upon interacting, the user will be granted a set of base building actions that will generally be carried out at the drone's location. * To create a new base builder system, this class should be the only thing that needs to be subtyped. @@ -61,8 +61,7 @@ var/turf/spawn_spot = find_spawn_spot() if (!spawn_spot) return FALSE - eyeobj = new /mob/eye/ai_eye/remote/base_construction(spawn_spot, src) - eyeobj.origin = src + eyeobj = new /mob/eye/camera/remote/base_construction(spawn_spot, src) return TRUE /obj/machinery/computer/camera_advanced/base_construction/attackby(obj/item/W, mob/user, params) @@ -95,7 +94,7 @@ * The mob is constrained to a given area defined by the base construction console. * */ -/mob/eye/ai_eye/remote/base_construction +/mob/eye/camera/remote/base_construction name = "construction holo-drone" //Allows any curious crew to watch the base after it leaves. (This is safe as the base cannot be modified once it leaves) move_on_shuttle = TRUE @@ -105,20 +104,20 @@ ///Reference to the camera console controlling this drone var/obj/machinery/computer/camera_advanced/base_construction/linked_console -/mob/eye/ai_eye/remote/base_construction/Initialize(mapload, obj/machinery/computer/camera_advanced/console_link) +/mob/eye/camera/remote/base_construction/Initialize(mapload, obj/machinery/computer/camera_advanced/console_link) linked_console = console_link if(!linked_console) stack_trace("A base consturuction drone was created with no linked console") return INITIALIZE_HINT_QDEL return ..() -/mob/eye/ai_eye/remote/base_construction/setLoc(turf/destination, force_update = FALSE) +/mob/eye/camera/remote/base_construction/setLoc(turf/destination, force_update = FALSE) var/area/curr_area = get_area(destination) //Only move if we're in the allowed area. If no allowed area is defined, then we're free to move wherever. if(!linked_console.allowed_area || istype(curr_area, linked_console.allowed_area)) return ..() -/mob/eye/ai_eye/remote/base_construction/relaymove(mob/living/user, direction) +/mob/eye/camera/remote/base_construction/relaymove(mob/living/user, direction) //This camera eye is visible, and as such needs to keep its dir updated dir = direction return ..() diff --git a/code/modules/antagonists/abductor/machinery/camera.dm b/code/modules/antagonists/abductor/machinery/camera.dm index f4ddd345075..644e3a827bf 100644 --- a/code/modules/antagonists/abductor/machinery/camera.dm +++ b/code/modules/antagonists/abductor/machinery/camera.dm @@ -21,11 +21,12 @@ return ..() /obj/machinery/computer/camera_advanced/abductor/CreateEye() - ..() - eyeobj.visible_icon = TRUE + . = ..() + //For observers eyeobj.icon = 'icons/mob/eyemob.dmi' eyeobj.icon_state = "abductor_camera" - eyeobj.SetInvisibility(INVISIBILITY_OBSERVER) + //For the user + eyeobj.set_user_icon(eyeobj.icon, eyeobj.icon_state) /obj/machinery/computer/camera_advanced/abductor/GrantActions(mob/living/carbon/user) if(!abduct_created) @@ -57,7 +58,7 @@ to_chat(owner, span_warning("You must wait [DisplayTimeText(use_delay - world.time)] to use the [target] again!")) return var/mob/living/carbon/human/C = owner - var/mob/eye/ai_eye/remote/remote_eye = C.remote_control + var/mob/eye/camera/remote/remote_eye = C.remote_control var/obj/machinery/abductor/pad/P = target var/area/target_area = get_area(remote_eye) @@ -101,7 +102,7 @@ to_chat(owner, span_warning("You can only teleport to one place at a time!")) return var/mob/living/carbon/human/C = owner - var/mob/eye/ai_eye/remote/remote_eye = C.remote_control + var/mob/eye/camera/remote/remote_eye = C.remote_control var/obj/machinery/abductor/pad/P = target var/area/target_area = get_area(remote_eye) @@ -151,7 +152,7 @@ return var/mob/living/carbon/human/C = owner - var/mob/eye/ai_eye/remote/remote_eye = C.remote_control + var/mob/eye/camera/remote/remote_eye = C.remote_control var/obj/machinery/abductor/console/console = target console.SetDroppoint(remote_eye.loc,owner) diff --git a/code/modules/forensics/_forensics.dm b/code/modules/forensics/_forensics.dm index 8058a32e7fa..450521e6c62 100644 --- a/code/modules/forensics/_forensics.dm +++ b/code/modules/forensics/_forensics.dm @@ -112,7 +112,7 @@ if(!iseyemob(suspect)) return if(isaicamera(suspect)) - var/mob/eye/ai_eye/ai_camera = suspect + var/mob/eye/camera/ai/ai_camera = suspect if(!ai_camera.ai) return suspect = ai_camera.ai @@ -193,7 +193,7 @@ if(!iseyemob(suspect)) return if(isaicamera(suspect)) - var/mob/eye/ai_eye/ai_camera = suspect + var/mob/eye/camera/ai/ai_camera = suspect if(!ai_camera.ai) return suspect = ai_camera.ai diff --git a/code/modules/mob/eye/camera/camera.dm b/code/modules/mob/eye/camera/camera.dm new file mode 100644 index 00000000000..9f3ddc7b2ee --- /dev/null +++ b/code/modules/mob/eye/camera/camera.dm @@ -0,0 +1,80 @@ +/** + * Eye mob used to look around a [camera network][/datum/cameranet]. \ + * As it moves, it makes requests to the network to update what the user can and cannot see. + */ +/mob/eye/camera + name = "Inactive Camera Eye" + icon = 'icons/mob/eyemob.dmi' + icon_state = "generic_camera" + + invisibility = INVISIBILITY_OBSERVER + interaction_range = INFINITY + /// If TRUE, the eye will cover turfs hidden to the cameranet with static. + var/use_visibility = TRUE + /// List of [camera chunks][/datum/camerachunk] visible to this camera. + /// Please don't interface with this directly. Use the [cameranet][/datum/cameranet]. + var/final/list/datum/camerachunk/visibleCameraChunks = list() + /// NxN Range of a single camera chunk. + var/static_visibility_range = 16 + +/mob/eye/camera/Initialize(mapload) + . = ..() + GLOB.camera_eyes += src + +/mob/eye/camera/Destroy() + for(var/datum/camerachunk/chunk in visibleCameraChunks) + chunk.remove(src) + GLOB.camera_eyes -= src + return ..() + +/** + * Getter proc for getting the current user's client. + * + * The base version of this proc returns null. + * Subtypes are expected to overload this proc and make it return something meaningful. + */ +/mob/eye/camera/proc/GetViewerClient() + RETURN_TYPE(/client) + SHOULD_BE_PURE(TRUE) + + return null + +/** + * Use this when setting the camera eye's location directly. \ + * It will also attempt to update visible chunks. + */ +/mob/eye/camera/proc/setLoc(destination, force_update = FALSE) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(TRUE) + + destination = get_turf(destination) + if(!force_update && (destination == get_turf(src))) + return + + if(destination) + abstract_move(destination) + else + moveToNullspace() + + if(use_visibility) + update_visibility() + update_parallax_contents() + +/// Sends a visibility query to the cameranet. +/// Can be used as a signal handler. +/mob/eye/camera/proc/update_visibility() + SIGNAL_HANDLER + PROTECTED_PROC(TRUE) + SHOULD_CALL_PARENT(TRUE) + + if(use_visibility) + GLOB.cameranet.visibility(src) + +/mob/eye/camera/zMove(dir, turf/target, z_move_flags = NONE, recursions_left = 1, list/falling_movs) + . = ..() + if(.) + setLoc(loc, force_update = TRUE) + +/mob/eye/camera/Move() + SHOULD_NOT_OVERRIDE(TRUE) + return diff --git a/code/modules/mob/eye/camera/remote.dm b/code/modules/mob/eye/camera/remote.dm new file mode 100644 index 00000000000..97fe41886ce --- /dev/null +++ b/code/modules/mob/eye/camera/remote.dm @@ -0,0 +1,130 @@ +/** + * A camera controlled by a machine-operating user, like advanced cameras. + * Handles assigning/unassigning it's users, as well as applying sight effects. + */ +/mob/eye/camera/remote + /// Weakref to the current user of this eye. Must be a [living mob][/mob/living]. + var/datum/weakref/user_ref + /// Weakref to the creator of this eye. Must be a [machine][/obj/machinery]. + var/datum/weakref/origin_ref + + /// TRUE if this camera should show itself to the user. + var/visible_to_user = FALSE + /// If visible_to_user is TRUE, it will show this in the center of the screen. + VAR_PROTECTED/image/user_image + + /* The below code could be shared by AI eyes... */ + + /// If TRUE, the eye will have acceleration when moving. + var/acceleration = TRUE + /// Used internally for calculating wait time. (world.timeofday + wait_time) + VAR_FINAL/last_moved = 0 + /// The amount of time that must pass before var/sprint is reset. + VAR_PROTECTED/wait_time = 5 DECISECONDS + /// The speed of the camera. Scales from initial(sprint) to var/max_sprint + VAR_PROTECTED/sprint = 10 + /// Amount of speed that is added to var/sprint. + VAR_PROTECTED/momentum = 0.5 + /// The maximum sprint that this eye can reach. + VAR_PROTECTED/max_sprint = 50 + + +/mob/eye/camera/remote/Initialize(mapload, obj/machinery/creator) + if(!creator) + return INITIALIZE_HINT_QDEL + + . = ..() + + origin_ref = WEAKREF(creator) + if(visible_to_user) + set_user_icon(icon, icon_state) + +/mob/eye/camera/remote/Destroy() + var/mob/living/user = user_ref?.resolve() + var/obj/machinery/origin = origin_ref?.resolve() + if(origin && user) + origin.remove_eye_control(user,src) + + assign_user(null) + origin_ref = null + return ..() + +/mob/eye/camera/remote/proc/assign_user(mob/living/new_user) + var/mob/living/old_user = user_ref?.resolve() + if(old_user) + old_user.remote_control = null + old_user.reset_perspective(null) + name = initial(src.name) + + var/client/old_user_client = GetViewerClient() + if(user_image && old_user_client) + old_user_client.images -= user_image + + user_ref = WEAKREF(new_user) //The user_ref can still be null! + + if(new_user) + new_user.remote_control = src + new_user.reset_perspective(src) + name = "Camera Eye ([new_user.name])" + + var/client/new_user_client = GetViewerClient() + if(user_image && new_user_client) + new_user_client.images += user_image + +/** + * Sets the camera's user image to this icon and state. + * If chosen_icon is null, the user image will be removed. + */ +/mob/eye/camera/remote/proc/set_user_icon(icon/chosen_icon, icon_state) + SHOULD_CALL_PARENT(TRUE) + + var/client/user_client = GetViewerClient() + + if(!isnull(chosen_icon)) + set_user_icon(null) //remove whatever the last icon was + if(!isicon(chosen_icon) || !(!isnull(icon_state) && istext(icon_state))) + CRASH("Tried to set [src]'s user_image with bad parameters") + + user_image = image(chosen_icon, src, icon_state, FLY_LAYER) + if(user_client) + user_client.images += user_image + else + if(user_client) + user_client.images -= user_image + QDEL_NULL(user_image) + +/mob/eye/camera/remote/update_remote_sight(mob/living/user) + user.set_invis_see(SEE_INVISIBLE_LIVING) //can't see ghosts through cameras + user.set_sight(SEE_TURFS) + return TRUE + +/mob/eye/camera/remote/GetViewerClient() + var/mob/living/user = user_ref?.resolve() + + if(user) + return user.client + return null + +/mob/eye/camera/remote/setLoc(turf/destination, force_update = FALSE) + . = ..() + + var/client/user_client = GetViewerClient() + if(user_image && user_client) + SET_PLANE(user_image, ABOVE_GAME_PLANE, destination) //incase we move a z-level + +/mob/eye/camera/remote/relaymove(mob/living/user, direction) + var/initial = initial(src.sprint) + + if(last_moved < world.timeofday) // It's been too long since we last moved, reset sprint + sprint = initial + + for(var/i = 0; i < max(sprint, initial); i += 20) + var/turf/step = get_turf(get_step(src, direction)) + if(step) + setLoc(step) + + last_moved = world.timeofday + wait_time + if(acceleration) + sprint = min(sprint + momentum, max_sprint) + else + sprint = initial diff --git a/code/modules/mob/eye/eye.dm b/code/modules/mob/eye/eye.dm index ca58e2969c5..54cb5e2b40a 100644 --- a/code/modules/mob/eye/eye.dm +++ b/code/modules/mob/eye/eye.dm @@ -1,4 +1,4 @@ -// Eye mob, used by cameras and overminds such as blobs. +/// Eye mob, used by cameras and overminds such as blobs. /mob/eye name = "eye mob" density = FALSE @@ -8,9 +8,9 @@ invisibility = INVISIBILITY_ABSTRACT // No one can see us sight = SEE_SELF status_flags = NONE - /// Toggles if the camera can move on shuttles + /// Toggles if the eye can move on shuttles var/move_on_shuttle = FALSE - /// Toggles if the camera can use emotes + /// Toggles if the eye can use emotes var/has_emotes = FALSE /mob/eye/Initialize(mapload) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index b8e755faade..46cfb5980a2 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -62,7 +62,7 @@ var/nuking = FALSE var/obj/machinery/doomsday_device/doomsday_device - var/mob/eye/ai_eye/eyeobj + var/mob/eye/camera/ai/eyeobj var/sprint = 10 var/last_moved = 0 var/acceleration = TRUE @@ -1139,8 +1139,8 @@ target_ai = src //cheat! just give... ourselves as the spawned AI, because that's technically correct . = ..() -/mob/living/silicon/ai/proc/camera_visibility(mob/eye/ai_eye/moved_eye) - GLOB.cameranet.visibility(moved_eye, client, all_eyes, TRUE) +/mob/living/silicon/ai/proc/camera_visibility(mob/eye/camera/ai/moved_eye) + GLOB.cameranet.visibility(moved_eye) /mob/living/silicon/ai/forceMove(atom/destination) . = ..() diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm index b8bd1f443ea..1f8c452f492 100644 --- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm +++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm @@ -12,7 +12,6 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) var/list/obj/machinery/camera/cameras = list() /// The chunks of the map, mapping the areas that the cameras can see. var/list/chunks = list() - var/ready = 0 /// List of images cloned by all chunk static images put onto turfs cameras cant see /// Indexed by the plane offset to use @@ -56,16 +55,12 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) if(!.) chunks[key] = . = new /datum/camerachunk(x, y, lowest.z) -/// Updates what the aiEye can see. It is recommended you use this when the aiEye moves or its location is set. -/datum/cameranet/proc/visibility(list/moved_eyes, client/C, list/other_eyes, use_static = TRUE) +/// Updates what the camera eye can see. It is recommended you use this when a camera eye moves or its location is set. +/datum/cameranet/proc/visibility(list/moved_eyes) if(!islist(moved_eyes)) moved_eyes = moved_eyes ? list(moved_eyes) : list() - if(islist(other_eyes)) - other_eyes = (other_eyes - moved_eyes) - else - other_eyes = list() - for(var/mob/eye/ai_eye/eye as anything in moved_eyes) + for(var/mob/eye/camera/eye as anything in moved_eyes) var/list/visibleChunks = list() //Get the eye's turf in case its located in an object like a mecha var/turf/eye_turf = get_turf(eye) @@ -129,6 +124,8 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) * to change the time between static updates. */ /datum/cameranet/proc/majorChunkChange(atom/c, choice, update_delay_buffer) + PROTECTED_PROC(TRUE) + if(QDELETED(c) && choice == 1) CRASH("Tried to add a qdeleting camera to the net") diff --git a/code/modules/mob/living/silicon/ai/freelook/chunk.dm b/code/modules/mob/living/silicon/ai/freelook/chunk.dm index 7b2c57abc3c..73908f162b8 100644 --- a/code/modules/mob/living/silicon/ai/freelook/chunk.dm +++ b/code/modules/mob/living/silicon/ai/freelook/chunk.dm @@ -1,9 +1,9 @@ #define UPDATE_BUFFER_TIME (2.5 SECONDS) -// CAMERA CHUNK -// -// A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed. -// Allows the AI Eye to stream these chunks and know what it can and cannot see. +/** + * A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed. \ + * Allows Camera Eyes to stream these chunks and know what it can and cannot see. + */ /datum/camerachunk ///turfs our cameras cant see but are inside our grid. associative list of the form: list(obscured turf = static image on that turf) @@ -16,7 +16,7 @@ ///list of all turfs, associative with that turf's static image ///turf -> /image var/list/turfs = list() - ///eye mobs that can see turfs in our grid + ///Camera mobs that can see turfs in our grid var/list/seenby = list() ///images currently in use on obscured turfs. var/list/active_static_images = list() @@ -27,24 +27,24 @@ var/lower_z var/upper_z -/// Add an AI eye to the chunk, then update if changed. -/datum/camerachunk/proc/add(mob/eye/ai_eye/eye) +/// Add a camera eye to the chunk, then update if changed. +/datum/camerachunk/proc/add(mob/eye/camera/eye) eye.visibleCameraChunks += src seenby += eye if(changed) update() var/client/client = eye.GetViewerClient() - if(client && eye.use_static) + if(client && eye.use_visibility) client.images += active_static_images -/// Remove an AI eye from the chunk -/datum/camerachunk/proc/remove(mob/eye/ai_eye/eye, remove_static_with_last_chunk = TRUE) +/// Remove a camera eye from the chunk +/datum/camerachunk/proc/remove(mob/eye/camera/ai/eye) eye.visibleCameraChunks -= src seenby -= eye var/client/client = eye.GetViewerClient() - if(client && eye.use_static) + if(client && eye.use_visibility) client.images -= active_static_images /// Called when a chunk has changed. I.E: A wall was deleted. @@ -56,6 +56,7 @@ /** * Updates the chunk, makes sure that it doesn't update too much. If the chunk isn't being watched it will * instead be flagged to update the next time an AI Eye moves near it. + * * update_delay_buffer is used for cameras that are moving around, which are cyborg inbuilt cameras and * mecha onboard cameras. This buffer should be usually lower than UPDATE_BUFFER_TIME because * otherwise a moving camera can run out of its own view before updating static. @@ -89,8 +90,8 @@ ///turfs that we could see last update but cant see now var/list/newly_obscured_turfs = visibleTurfs - updated_visible_turfs - for(var/mob/eye/ai_eye/client_eye as anything in seenby) - var/client/client = client_eye.ai?.client || client_eye.client + for(var/mob/eye/camera/client_eye as anything in seenby) + var/client/client = client_eye.GetViewerClient() if(!client) continue @@ -119,8 +120,8 @@ changed = FALSE - for(var/mob/eye/ai_eye/client_eye as anything in seenby) - var/client/client = client_eye.ai?.client || client_eye.client + for(var/mob/eye/camera/client_eye as anything in seenby) + var/client/client = client_eye.GetViewerClient() if(!client) continue diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index c93054d6d26..25ccafe26c0 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -1,43 +1,50 @@ -// AI EYE -// -// An invisible (no icon) mob that the AI controls to look around the station with. -// It streams chunks as it moves around, which will show it what the AI can and cannot see. -/mob/eye/ai_eye +/mob/eye/camera/ai name = "Inactive AI Eye" - icon_state = "ai_camera" - icon = 'icons/mob/eyemob.dmi' - invisibility = INVISIBILITY_MAXIMUM + hud_possible = list(ANTAG_HUD, AI_DETECT_HUD = HUD_LIST_LIST) - var/list/visibleCameraChunks = list() + /// The AI who owns this eye. var/mob/living/silicon/ai/ai = null + /// Whether this eye will transmit speech near it to the AI. var/relay_speech = FALSE - var/use_static = TRUE - var/static_visibility_range = 16 + /// Whether this eye can be found with AI detectors. var/ai_detector_visible = TRUE + /// The color of the area if the eye is detectable. var/ai_detector_color = COLOR_RED - interaction_range = INFINITY -/mob/eye/ai_eye/Initialize(mapload) +/mob/eye/camera/ai/Initialize(mapload) . = ..() - GLOB.aiEyes += src - update_ai_detect_hud() - setLoc(loc, TRUE) - -/mob/eye/ai_eye/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents) - . = ..() - if(same_z_layer) - return update_ai_detect_hud() -/mob/eye/ai_eye/examine(mob/user) //Displays a silicon's laws to ghosts - . = ..() - if(istype(ai) && ai.laws && isobserver(user)) - . += "[ai] has the following laws:" - for(var/law in ai.laws.get_law_list(include_zeroth = TRUE)) - . += law +/mob/eye/camera/ai/Destroy() + if(ai) + ai.all_eyes -= src + ai = null + if(ai_detector_visible) + var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] + hud.remove_atom_from_hud(src) + var/list/L = hud_list[AI_DETECT_HUD] + QDEL_LIST(L) + return ..() -/mob/eye/ai_eye/proc/update_ai_detect_hud() +/** + * Returns a list of turfs visible to the client's viewsize. \ + * Note that this will return an empty list if the camera's loc is not a turf. + */ +/mob/eye/camera/ai/proc/get_visible_turfs() + RETURN_TYPE(/list/turf) + SHOULD_BE_PURE(TRUE) + SHOULD_CALL_PARENT(TRUE) + + if(!isturf(loc)) + return list() + var/client/C = GetViewerClient() + var/view = C ? getviewsize(C.view) : getviewsize(world.view) + var/turf/lowerleft = locate(max(1, x - (view[1] - 1)/2), max(1, y - (view[2] - 1)/2), z) + var/turf/upperright = locate(min(world.maxx, lowerleft.x + (view[1] - 1)), min(world.maxy, lowerleft.y + (view[2] - 1)), lowerleft.z) + return block(lowerleft, upperright) + +/mob/eye/camera/ai/proc/update_ai_detect_hud() var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] var/list/old_images = hud_list[AI_DETECT_HUD] if(!ai_detector_visible) @@ -75,42 +82,17 @@ active_hud_list[AI_DETECT_HUD] = new_images hud.add_atom_to_hud(src) -/mob/eye/ai_eye/proc/get_visible_turfs() - if(!isturf(loc)) - return list() - var/client/C = GetViewerClient() - var/view = C ? getviewsize(C.view) : getviewsize(world.view) - var/turf/lowerleft = locate(max(1, x - (view[1] - 1)/2), max(1, y - (view[2] - 1)/2), z) - var/turf/upperright = locate(min(world.maxx, lowerleft.x + (view[1] - 1)), min(world.maxy, lowerleft.y + (view[2] - 1)), lowerleft.z) - return block(lowerleft, upperright) - -/// Used in cases when the eye is located in a movable object (i.e. mecha) -/mob/eye/ai_eye/proc/update_visibility() - SIGNAL_HANDLER - if(use_static) - ai.camera_visibility(src) - -// Use this when setting the aiEye's location. -// It will also stream the chunk that the new loc is in. - -/mob/eye/ai_eye/proc/setLoc(destination, force_update = FALSE) +/mob/eye/camera/ai/setLoc(destination, force_update = FALSE) if(!ai) return if(!isturf(ai.loc)) return - destination = get_turf(destination) - if(!force_update && (destination == get_turf(src))) - return //we are already here! - if (destination) - abstract_move(destination) - else - moveToNullspace() - if(use_static) - ai.camera_visibility(src) + + . = ..() + if(ai.client && !ai.multicam_on) ai.client.set_eye(src) update_ai_detect_hud() - update_parallax_contents() //Holopad if(istype(ai.current, /obj/machinery/holopad)) var/obj/machinery/holopad/H = ai.current @@ -122,33 +104,31 @@ if(ai.master_multicam) ai.master_multicam.refresh_view() -/mob/eye/ai_eye/zMove(dir, turf/target, z_move_flags = NONE, recursions_left = 1, list/falling_movs) - . = ..() - if(.) - setLoc(loc, force_update = TRUE) +/mob/eye/camera/ai/update_visibility() + if(ai) + ai.camera_visibility(src) + else + ..() -/mob/eye/ai_eye/Move() - return - -/mob/eye/ai_eye/proc/GetViewerClient() +/mob/eye/camera/ai/GetViewerClient() if(ai) return ai.client return null -/mob/eye/ai_eye/Destroy() - if(ai) - ai.all_eyes -= src - ai = null - for(var/V in visibleCameraChunks) - var/datum/camerachunk/c = V - c.remove(src) - GLOB.aiEyes -= src - if(ai_detector_visible) - var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] - hud.remove_atom_from_hud(src) - var/list/L = hud_list[AI_DETECT_HUD] - QDEL_LIST(L) - return ..() +/mob/eye/camera/ai/examine(mob/user) //Displays a silicon's laws to ghosts + . = ..() + if(istype(ai) && ai.laws && isobserver(user)) + . += "[ai] has the following laws:" + for(var/law in ai.laws.get_law_list(include_zeroth = TRUE)) + . += law + +/mob/eye/camera/ai/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents) + . = ..() + if(same_z_layer) + return + update_ai_detect_hud() + +/*----------------------------------------------------*/ /atom/proc/move_camera_by_click() if(!isAI(usr)) @@ -189,7 +169,6 @@ sprint = initial(sprint) ai_tracking_tool.reset_tracking() - #undef SPRINT_PER_STEP #undef MAX_SPRINT #undef SPRINT_PER_TICK @@ -215,12 +194,11 @@ /mob/living/silicon/ai/proc/create_eye() if(eyeobj) return - eyeobj = new /mob/eye/ai_eye() + eyeobj = new /mob/eye/camera/ai() all_eyes += eyeobj eyeobj.ai = src - eyeobj.setLoc(loc) eyeobj.name = "[name] (AI Eye)" - eyeobj.real_name = eyeobj.name + eyeobj.setLoc(loc, TRUE) set_eyeobj_visible(TRUE) /mob/living/silicon/ai/proc/set_eyeobj_visible(state = TRUE) @@ -241,7 +219,7 @@ acceleration = !acceleration to_chat(usr, "Camera acceleration has been toggled [acceleration ? "on" : "off"].") -/mob/eye/ai_eye/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), message_range) +/mob/eye/camera/ai/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), message_range) . = ..() if(relay_speech && speaker && ai && !radio_freq && speaker != ai && GLOB.cameranet.checkCameraVis(speaker)) ai.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mods) diff --git a/code/modules/mob/living/silicon/ai/multicam.dm b/code/modules/mob/living/silicon/ai/multicam.dm index 12ee1ce3b53..049243d90f1 100644 --- a/code/modules/mob/living/silicon/ai/multicam.dm +++ b/code/modules/mob/living/silicon/ai/multicam.dm @@ -4,11 +4,11 @@ var/mob/living/silicon/ai/ai var/mutable_appearance/highlighted_background var/highlighted = FALSE - var/mob/eye/ai_eye/pic_in_pic/aiEye + var/mob/eye/camera/ai/pic_in_pic/aiEye /atom/movable/screen/movable/pic_in_pic/ai/Initialize(mapload, datum/hud/hud_owner) . = ..() - aiEye = new /mob/eye/ai_eye/pic_in_pic() + aiEye = new /mob/eye/camera/ai/pic_in_pic() aiEye.screen = src /atom/movable/screen/movable/pic_in_pic/ai/Destroy() @@ -126,37 +126,38 @@ GLOBAL_DATUM(ai_camera_room_landmark, /obj/effect/landmark/ai_multicam_room) //Dummy camera eyes -/mob/eye/ai_eye/pic_in_pic +/mob/eye/camera/ai/pic_in_pic name = "Secondary AI Eye" + icon_state = "ai_pip_camera" invisibility = INVISIBILITY_OBSERVER mouse_opacity = MOUSE_OPACITY_ICON - icon_state = "ai_pip_camera" + ai_detector_color = COLOR_ORANGE + var/atom/movable/screen/movable/pic_in_pic/ai/screen var/list/cameras_telegraphed = list() var/telegraph_cameras = TRUE var/telegraph_range = 7 - ai_detector_color = COLOR_ORANGE -/mob/eye/ai_eye/pic_in_pic/GetViewerClient() +/mob/eye/camera/ai/pic_in_pic/GetViewerClient() if(screen?.ai) return screen.ai.client -/mob/eye/ai_eye/pic_in_pic/setLoc(turf/destination, force_update = FALSE) - if (destination) - abstract_move(destination) - else - moveToNullspace() +/mob/eye/camera/ai/pic_in_pic/update_visibility() if(screen?.ai) screen.ai.camera_visibility(src) else - GLOB.cameranet.visibility(src) + ..() + +/mob/eye/camera/ai/pic_in_pic/setLoc(turf/destination, force_update = FALSE) + . = ..() update_camera_telegraphing() update_ai_detect_hud() -/mob/eye/ai_eye/pic_in_pic/get_visible_turfs() +/mob/eye/camera/ai/pic_in_pic/get_visible_turfs() + SHOULD_CALL_PARENT(FALSE) //we do our own thing here return screen ? screen.get_visible_turfs() : list() -/mob/eye/ai_eye/pic_in_pic/proc/update_camera_telegraphing() +/mob/eye/camera/ai/pic_in_pic/proc/update_camera_telegraphing() if(!telegraph_cameras) return var/list/obj/machinery/camera/add = list() @@ -185,7 +186,7 @@ GLOBAL_DATUM(ai_camera_room_landmark, /obj/effect/landmark/ai_multicam_room) C.in_use_lights++ C.update_appearance() -/mob/eye/ai_eye/pic_in_pic/proc/disable_camera_telegraphing() +/mob/eye/camera/ai/pic_in_pic/proc/disable_camera_telegraphing() telegraph_cameras = FALSE for (var/obj/machinery/camera/C as anything in cameras_telegraphed) if(QDELETED(C)) @@ -194,7 +195,7 @@ GLOBAL_DATUM(ai_camera_room_landmark, /obj/effect/landmark/ai_multicam_room) C.update_appearance() cameras_telegraphed.Cut() -/mob/eye/ai_eye/pic_in_pic/Destroy() +/mob/eye/camera/ai/pic_in_pic/Destroy() disable_camera_telegraphing() return ..() diff --git a/code/modules/research/xenobiology/xenobio_camera.dm b/code/modules/research/xenobiology/xenobio_camera.dm index fc6d236f96a..66cbd4b93de 100644 --- a/code/modules/research/xenobiology/xenobio_camera.dm +++ b/code/modules/research/xenobiology/xenobio_camera.dm @@ -1,22 +1,20 @@ //Xenobio control console -/mob/eye/ai_eye/remote/xenobio - visible_icon = TRUE - icon = 'icons/mob/eyemob.dmi' - icon_state = "generic_camera" +/mob/eye/camera/remote/xenobio + visible_to_user = TRUE var/allowed_area = null -/mob/eye/ai_eye/remote/xenobio/Initialize(mapload) +/mob/eye/camera/remote/xenobio/Initialize(mapload) var/area/our_area = get_area(loc) allowed_area = our_area.name . = ..() -/mob/eye/ai_eye/remote/xenobio/setLoc(turf/destination, force_update = FALSE) +/mob/eye/camera/remote/xenobio/setLoc(turf/destination, force_update = FALSE) var/area/new_area = get_area(destination) if(new_area && new_area.name == allowed_area || new_area && (new_area.area_flags & XENOBIOLOGY_COMPATIBLE)) return ..() -/mob/eye/ai_eye/remote/xenobio/can_z_move(direction, turf/start, turf/destination, z_move_flags = NONE, mob/living/rider) +/mob/eye/camera/remote/xenobio/can_z_move(direction, turf/start, turf/destination, z_move_flags = NONE, mob/living/rider) . = ..() if(!.) return @@ -84,11 +82,9 @@ stored_slimes -= gone /obj/machinery/computer/camera_advanced/xenobio/CreateEye() - eyeobj = new /mob/eye/ai_eye/remote/xenobio(get_turf(src)) - eyeobj.origin = src - eyeobj.visible_icon = TRUE - eyeobj.icon = 'icons/mob/eyemob.dmi' - eyeobj.icon_state = "generic_camera" + eyeobj = new /mob/eye/camera/remote/xenobio(get_turf(src), src) + + return TRUE /obj/machinery/computer/camera_advanced/xenobio/GrantActions(mob/living/user) . = ..() @@ -151,7 +147,7 @@ Boilerplate check for a valid area to perform a camera action in. Checks if the AI eye is on a valid turf and then checks if the target turf is xenobiology compatible Due to keyboard shortcuts, the second one is not necessarily the remote eye's location. */ -/obj/machinery/computer/camera_advanced/xenobio/proc/validate_area(mob/living/user, mob/eye/ai_eye/remote/xenobio/remote_eye, turf/open/target_turf) +/obj/machinery/computer/camera_advanced/xenobio/proc/validate_area(mob/living/user, mob/eye/camera/remote/xenobio/remote_eye, turf/open/target_turf) if(!GLOB.cameranet.checkTurfVis(remote_eye.loc)) to_chat(user, span_warning("Target is not near a camera. Cannot proceed.")) return FALSE @@ -228,7 +224,7 @@ Due to keyboard shortcuts, the second one is not necessarily the remote eye's lo if(!target || !isliving(owner)) return var/mob/living/owner_mob = owner - var/mob/eye/ai_eye/remote/xenobio/remote_eye = owner_mob.remote_control + var/mob/eye/camera/remote/xenobio/remote_eye = owner_mob.remote_control var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = target if(!xeno_console.validate_area(owner, remote_eye, remote_eye.loc)) @@ -245,7 +241,7 @@ Due to keyboard shortcuts, the second one is not necessarily the remote eye's lo if(!target || !isliving(owner)) return var/mob/living/owner_mob = owner - var/mob/eye/ai_eye/remote/xenobio/remote_eye = owner_mob.remote_control + var/mob/eye/camera/remote/xenobio/remote_eye = owner_mob.remote_control var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = target if(!xeno_console.validate_area(owner, remote_eye, remote_eye.loc)) @@ -264,7 +260,7 @@ Due to keyboard shortcuts, the second one is not necessarily the remote eye's lo if(!target || !isliving(owner)) return var/mob/living/living_owner = owner - var/mob/eye/ai_eye/remote/xenobio/remote_eye = living_owner.remote_control + var/mob/eye/camera/remote/xenobio/remote_eye = living_owner.remote_control var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = target if(!xeno_console.validate_area(owner, remote_eye, remote_eye.loc)) @@ -282,7 +278,7 @@ Due to keyboard shortcuts, the second one is not necessarily the remote eye's lo if(!target || !isliving(owner)) return var/mob/living/owner_mob = owner - var/mob/eye/ai_eye/remote/xenobio/remote_eye = owner_mob.remote_control + var/mob/eye/camera/remote/xenobio/remote_eye = owner_mob.remote_control var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = target var/obj/machinery/monkey_recycler/recycler = xeno_console.connected_recycler @@ -305,7 +301,7 @@ Due to keyboard shortcuts, the second one is not necessarily the remote eye's lo if(!target || !isliving(owner)) return var/mob/living/owner_mob = owner - var/mob/eye/ai_eye/remote/xenobio/remote_eye = owner_mob.remote_control + var/mob/eye/camera/remote/xenobio/remote_eye = owner_mob.remote_control var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = target if(!xeno_console.validate_area(owner, remote_eye, remote_eye.loc)) @@ -324,7 +320,7 @@ Due to keyboard shortcuts, the second one is not necessarily the remote eye's lo return var/mob/living/owner_mob = owner - var/mob/eye/ai_eye/remote/xenobio/remote_eye = owner_mob.remote_control + var/mob/eye/camera/remote/xenobio/remote_eye = owner_mob.remote_control var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = target if(!xeno_console.validate_area(owner, remote_eye, remote_eye.loc)) @@ -375,8 +371,8 @@ Due to keyboard shortcuts, the second one is not necessarily the remote eye's lo if(!isslime(target_slime)) return - var/mob/eye/ai_eye/remote/xenobio/remote_eye = user.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin + var/mob/eye/camera/remote/xenobio/remote_eye = user.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin_ref.resolve() if(!xeno_console.validate_area(user, remote_eye, target_slime.loc)) return @@ -391,8 +387,8 @@ Due to keyboard shortcuts, the second one is not necessarily the remote eye's lo /obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickShift(mob/living/user, mob/living/basic/slime/target_slime) SIGNAL_HANDLER - var/mob/eye/ai_eye/remote/xenobio/remote_eye = user.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin + var/mob/eye/camera/remote/xenobio/remote_eye = user.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin_ref.resolve() if(!xeno_console.validate_area(user, remote_eye, target_slime.loc)) return @@ -404,8 +400,8 @@ Due to keyboard shortcuts, the second one is not necessarily the remote eye's lo SIGNAL_HANDLER var/mob/living/user_mob = user - var/mob/eye/ai_eye/remote/xenobio/remote_eye = user_mob.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin + var/mob/eye/camera/remote/xenobio/remote_eye = user_mob.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin_ref.resolve() if(!xeno_console.validate_area(user, remote_eye, target_turf)) return @@ -430,8 +426,8 @@ Due to keyboard shortcuts, the second one is not necessarily the remote eye's lo return var/cleanup = FALSE - var/mob/eye/ai_eye/remote/xenobio/remote_eye = user.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin + var/mob/eye/camera/remote/xenobio/remote_eye = user.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin_ref.resolve() if(!xeno_console.validate_area(user, remote_eye, target_turf)) return @@ -449,8 +445,8 @@ Due to keyboard shortcuts, the second one is not necessarily the remote eye's lo if(!ismonkey(target_mob)) return - var/mob/eye/ai_eye/remote/xenobio/remote_eye = user.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin + var/mob/eye/camera/remote/xenobio/remote_eye = user.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin_ref.resolve() if(!xeno_console.connected_recycler) to_chat(user, span_warning("There is no connected monkey recycler. Use a multitool to link one.")) @@ -466,8 +462,8 @@ Due to keyboard shortcuts, the second one is not necessarily the remote eye's lo if(!isslime(target_slime)) return - var/mob/eye/ai_eye/remote/xenobio/remote_eye = user.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin + var/mob/eye/camera/remote/xenobio/remote_eye = user.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin_ref.resolve() if(!xeno_console.validate_area(user, remote_eye, target_slime.loc)) return diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/navigation_computer.dm index fbe528b8703..7c588e06dc0 100644 --- a/code/modules/shuttle/navigation_computer.dm +++ b/code/modules/shuttle/navigation_computer.dm @@ -107,8 +107,8 @@ shuttle_port = null return - eyeobj = new /mob/eye/ai_eye/remote/shuttle_docker(null, src) - var/mob/eye/ai_eye/remote/shuttle_docker/the_eye = eyeobj + eyeobj = new /mob/eye/camera/remote/shuttle_docker(null, src) + var/mob/eye/camera/remote/shuttle_docker/the_eye = eyeobj the_eye.setDir(shuttle_port.dir) var/turf/origin = locate(shuttle_port.x + x_offset, shuttle_port.y + y_offset, shuttle_port.z) for(var/area/shuttle_area as anything in shuttle_port.shuttle_areas) @@ -125,10 +125,12 @@ I.mouse_opacity = MOUSE_OPACITY_TRANSPARENT the_eye.placement_images[I] = list(x_off, y_off) + return TRUE + /obj/machinery/computer/camera_advanced/shuttle_docker/give_eye_control(mob/user) ..() if(!QDELETED(user) && user.client) - var/mob/eye/ai_eye/remote/shuttle_docker/the_eye = eyeobj + var/mob/eye/camera/remote/shuttle_docker/the_eye = eyeobj var/list/to_add = list() to_add += the_eye.placement_images to_add += the_eye.placed_images @@ -141,7 +143,7 @@ /obj/machinery/computer/camera_advanced/shuttle_docker/remove_eye_control(mob/living/user) ..() if(!QDELETED(user) && user.client) - var/mob/eye/ai_eye/remote/shuttle_docker/the_eye = eyeobj + var/mob/eye/camera/remote/shuttle_docker/the_eye = eyeobj var/list/to_remove = list() to_remove += the_eye.placement_images to_remove += the_eye.placed_images @@ -155,7 +157,7 @@ if(designating_target_loc || !current_user) return - var/mob/eye/ai_eye/remote/shuttle_docker/the_eye = eyeobj + var/mob/eye/camera/remote/shuttle_docker/the_eye = eyeobj var/landing_clear = checkLandingSpot() if(designate_time && (landing_clear != SHUTTLE_DOCKER_BLOCKED)) to_chat(current_user, span_warning("Targeting transit location, please wait [DisplayTimeText(designate_time)]...")) @@ -223,7 +225,7 @@ return TRUE /obj/machinery/computer/camera_advanced/shuttle_docker/proc/rotateLandingSpot() - var/mob/eye/ai_eye/remote/shuttle_docker/the_eye = eyeobj + var/mob/eye/camera/remote/shuttle_docker/the_eye = eyeobj var/list/image_cache = the_eye.placement_images the_eye.setDir(turn(the_eye.dir, -90)) for(var/i in 1 to image_cache.len) @@ -239,7 +241,7 @@ checkLandingSpot() /obj/machinery/computer/camera_advanced/shuttle_docker/proc/checkLandingSpot() - var/mob/eye/ai_eye/remote/shuttle_docker/the_eye = eyeobj + var/mob/eye/camera/remote/shuttle_docker/the_eye = eyeobj var/turf/eyeturf = get_turf(the_eye) if(!eyeturf) return SHUTTLE_DOCKER_BLOCKED @@ -316,22 +318,17 @@ add_jumpable_port(dock.shuttle_id) return TRUE -/mob/eye/ai_eye/remote/shuttle_docker - visible_icon = FALSE - use_static = FALSE +/mob/eye/camera/remote/shuttle_docker + use_visibility = FALSE var/list/image/placement_images = list() var/list/image/placed_images = list() -/mob/eye/ai_eye/remote/shuttle_docker/Initialize(mapload, obj/machinery/computer/camera_advanced/origin) - src.origin = origin - return ..() - -/mob/eye/ai_eye/remote/shuttle_docker/setLoc(turf/destination, force_update = FALSE) +/mob/eye/camera/remote/shuttle_docker/setLoc(turf/destination, force_update = FALSE) . = ..() - var/obj/machinery/computer/camera_advanced/shuttle_docker/console = origin + var/obj/machinery/computer/camera_advanced/shuttle_docker/console = origin_ref?.resolve() console.checkLandingSpot() -/mob/eye/ai_eye/remote/shuttle_docker/update_remote_sight(mob/living/user) +/mob/eye/camera/remote/shuttle_docker/update_remote_sight(mob/living/user) user.set_sight(BLIND|SEE_TURFS) // Pale blue, should look nice I think user.lighting_color_cutoffs = list(30, 40, 50) @@ -346,8 +343,8 @@ /datum/action/innate/shuttledocker_rotate/Activate() if(QDELETED(owner) || !isliving(owner)) return - var/mob/eye/ai_eye/remote/remote_eye = owner.remote_control - var/obj/machinery/computer/camera_advanced/shuttle_docker/origin = remote_eye.origin + var/mob/eye/camera/remote/remote_eye = owner.remote_control + var/obj/machinery/computer/camera_advanced/shuttle_docker/origin = remote_eye.origin_ref.resolve() origin.rotateLandingSpot() /datum/action/innate/shuttledocker_place @@ -358,8 +355,8 @@ /datum/action/innate/shuttledocker_place/Activate() if(QDELETED(owner) || !isliving(owner)) return - var/mob/eye/ai_eye/remote/remote_eye = owner.remote_control - var/obj/machinery/computer/camera_advanced/shuttle_docker/origin = remote_eye.origin + var/mob/eye/camera/remote/remote_eye = owner.remote_control + var/obj/machinery/computer/camera_advanced/shuttle_docker/origin = remote_eye.origin_ref.resolve() origin.placeLandingSpot(owner) /datum/action/innate/camera_jump/shuttle_docker @@ -369,8 +366,8 @@ /datum/action/innate/camera_jump/shuttle_docker/Activate() if(QDELETED(owner) || !isliving(owner)) return - var/mob/eye/ai_eye/remote/remote_eye = owner.remote_control - var/obj/machinery/computer/camera_advanced/shuttle_docker/console = remote_eye.origin + var/mob/eye/camera/remote/remote_eye = owner.remote_control + var/obj/machinery/computer/camera_advanced/shuttle_docker/console = remote_eye.origin_ref.resolve() playsound(console, 'sound/machines/terminal/terminal_prompt_deny.ogg', 25, FALSE) diff --git a/code/modules/unit_tests/mob_faction.dm b/code/modules/unit_tests/mob_faction.dm index f5d64918038..be667de731d 100644 --- a/code/modules/unit_tests/mob_faction.dm +++ b/code/modules/unit_tests/mob_faction.dm @@ -10,8 +10,8 @@ ) ignored += typesof(/mob/eye/imaginary_friend) ignored += typesof(/mob/living/silicon/robot/model) - ignored += typesof(/mob/eye/ai_eye/remote/base_construction) - ignored += typesof(/mob/eye/ai_eye/remote/shuttle_docker) + ignored += typesof(/mob/eye/camera/remote/base_construction) + ignored += typesof(/mob/eye/camera/remote/shuttle_docker) for (var/mob_type in typesof(/mob) - ignored) var/mob/mob_instance = allocate(mob_type) if(!islist(mob_instance.faction)) diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm index ce59bd3d61d..f5dee3e11ee 100644 --- a/code/modules/unit_tests/unit_test.dm +++ b/code/modules/unit_tests/unit_test.dm @@ -289,9 +289,9 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests()) //No heart to give returnable_list += typesof(/obj/structure/ethereal_crystal) //No linked console - returnable_list += typesof(/mob/eye/ai_eye/remote/base_construction) + returnable_list += typesof(/mob/eye/camera/remote/base_construction) //See above - returnable_list += typesof(/mob/eye/ai_eye/remote/shuttle_docker) + returnable_list += typesof(/mob/eye/camera/remote/shuttle_docker) //Hangs a ref post invoke async, which we don't support. Could put a qdeleted check but it feels hacky returnable_list += typesof(/obj/effect/anomaly/grav/high) //See above diff --git a/code/modules/vehicles/mecha/mecha_ai_interaction.dm b/code/modules/vehicles/mecha/mecha_ai_interaction.dm index 6dc1e2307dc..168b2e0ea02 100644 --- a/code/modules/vehicles/mecha/mecha_ai_interaction.dm +++ b/code/modules/vehicles/mecha/mecha_ai_interaction.dm @@ -99,7 +99,7 @@ mecha_flags |= SILICON_PILOT moved_inside(AI) AI.eyeobj?.forceMove(src) - AI.eyeobj?.RegisterSignal(src, COMSIG_MOVABLE_MOVED, TYPE_PROC_REF(/mob/eye/ai_eye, update_visibility)) + AI.eyeobj?.RegisterSignal(src, COMSIG_MOVABLE_MOVED, TYPE_PROC_REF(/mob/eye/camera/ai, update_visibility)) AI.controlled_equipment = src AI.remote_control = src add_occupant(AI) diff --git a/tgstation.dme b/tgstation.dme index 51f540d2c85..c83d7d87c42 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4799,6 +4799,8 @@ #include "code\modules\mob\dead\observer\observer_say.dm" #include "code\modules\mob\dead\observer\orbit.dm" #include "code\modules\mob\eye\eye.dm" +#include "code\modules\mob\eye\camera\camera.dm" +#include "code\modules\mob\eye\camera\remote.dm" #include "code\modules\mob\living\blood.dm" #include "code\modules\mob\living\damage_procs.dm" #include "code\modules\mob\living\death.dm"